package com.engine.salary.service.impl;

import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.lang.Validator;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONArray;
import com.api.formmode.mybatis.util.SqlProxyHandle;
import com.cloudstore.dev.api.bean.MessageBean;
import com.cloudstore.dev.api.bean.MessageType;
import com.cloudstore.dev.api.util.Util_Message;
import com.engine.common.util.ServiceUtil;
import com.engine.core.impl.Service;
import com.engine.hrmelog.entity.dto.LoggerContext;
import com.engine.salary.biz.SalarySendBiz;
import com.engine.salary.biz.SalarySendInfoBiz;
import com.engine.salary.config.SalaryElogConfig;
import com.engine.salary.constant.SalaryArchiveConstant;
import com.engine.salary.constant.SalaryItemConstant;
import com.engine.salary.constant.SalaryTemplateSalaryItemSetGroupConstant;
import com.engine.salary.encrypt.AESEncryptUtil;
import com.engine.salary.encrypt.EncryptUtil;
import com.engine.salary.entity.datacollection.AddUpSituation;
import com.engine.salary.entity.datacollection.DataCollectionEmployee;
import com.engine.salary.entity.salaryBill.bo.SalaryBillBO;
import com.engine.salary.entity.salaryBill.dto.*;
import com.engine.salary.entity.salaryBill.param.*;
import com.engine.salary.entity.salaryBill.po.SalarySendInfoPO;
import com.engine.salary.entity.salaryBill.po.SalarySendPO;
import com.engine.salary.entity.salaryBill.po.SalaryTemplatePO;
import com.engine.salary.entity.salaryacct.bo.SalaryAcctEmployeeBO;
import com.engine.salary.entity.salaryacct.bo.SalaryAcctFormulaBO;
import com.engine.salary.entity.salaryacct.bo.SalaryAcctResultBO;
import com.engine.salary.entity.salaryacct.param.SalaryAcctResultQueryParam;
import com.engine.salary.entity.salaryacct.po.SalaryAcctEmployeePO;
import com.engine.salary.entity.salaryacct.po.SalaryAcctRecordPO;
import com.engine.salary.entity.salaryacct.po.SalaryAcctResultPO;
import com.engine.salary.entity.salaryformula.dto.SalaryFormulaEmployeeDTO;
import com.engine.salary.entity.salaryitem.po.SalaryItemPO;
import com.engine.salary.entity.salarysob.dto.SalarySobCycleDTO;
import com.engine.salary.entity.salarysob.po.SalarySobBackItemPO;
import com.engine.salary.entity.salarysob.po.SalarySobItemPO;
import com.engine.salary.entity.salarysob.po.SalarySobPO;
import com.engine.salary.entity.taxagent.po.TaxAgentPO;
import com.engine.salary.enums.AccountTypeEnum;
import com.engine.salary.enums.OperateTypeEnum;
import com.engine.salary.enums.UserStatusEnum;
import com.engine.salary.enums.salaryaccounting.SalaryAcctRecordStatusEnum;
import com.engine.salary.enums.salaryarchive.SalaryArchiveFieldTypeEnum;
import com.engine.salary.enums.salarybill.*;
import com.engine.salary.enums.salaryitem.SalaryDataTypeEnum;
import com.engine.salary.enums.salarysend.SalarySendGrantTypeEnum;
import com.engine.salary.exception.SalaryRunTimeException;
import com.engine.salary.mapper.SQLMapper;
import com.engine.salary.mapper.salaryacct.SalaryAcctEmployeeMapper;
import com.engine.salary.mapper.salaryacct.SalaryAcctRecordMapper;
import com.engine.salary.mapper.salaryacct.SalaryAcctResultMapper;
import com.engine.salary.mapper.salarybill.SalarySendInfoMapper;
import com.engine.salary.mapper.salarybill.SalarySendMapper;
import com.engine.salary.report.common.constant.SalaryConstant;
import com.engine.salary.service.*;
import com.engine.salary.service.auth.AuthService;
import com.engine.salary.service.auth.AuthServiceImpl;
import com.engine.salary.sys.entity.vo.OrderRuleVO;
import com.engine.salary.sys.service.SalarySysConfService;
import com.engine.salary.sys.service.impl.SalarySysConfServiceImpl;
import com.engine.salary.util.JsonUtil;
import com.engine.salary.util.SalaryDateUtil;
import com.engine.salary.util.SalaryEntityUtil;
import com.engine.salary.util.SalaryI18nUtil;
import com.engine.salary.util.db.IdGenerator;
import com.engine.salary.util.db.MapperProxyFactory;
import com.engine.salary.util.excel.ExcelUtil;
import com.engine.salary.util.page.PageInfo;
import com.engine.salary.util.page.SalaryPageUtil;
import com.google.common.collect.Lists;
import org.apache.commons.beanutils.BeanUtils;
import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.math.NumberUtils;
import org.apache.poi.xssf.usermodel.XSSFWorkbook;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import weaver.common.MessageUtil;
import weaver.conn.RecordSet;
import weaver.general.Util;
import weaver.hrm.User;
import weaver.hrm.company.SubCompanyComInfo;
import weaver.hrm.resource.ResourceComInfo;

import java.io.IOException;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.math.BigDecimal;
import java.text.SimpleDateFormat;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.ZoneOffset;
import java.util.*;
import java.util.stream.Collectors;

import static com.engine.salary.cache.SalaryCacheKey.SALARY_CACHE_SMS_CODE;
import static com.engine.salary.constant.HrmSalaryPayrollConf.GEN_PDF;
import static com.engine.salary.constant.HrmSalaryPayrollConf.baseBean;

/**
 * @Description: 工资单发放
 * @Author: wangxiangzhong
 * @Date: 2021-12-11 11:28
 */
public class SalarySendServiceImpl extends Service implements SalarySendService {

    private static final Logger log = LoggerFactory.getLogger(SalarySendServiceImpl.class);

    private EncryptUtil encryptUtil = new EncryptUtil();

    private SalarySendBiz mapper = new SalarySendBiz();
    private SalarySendInfoBiz salarySendInfoMapper = new SalarySendInfoBiz();

    private SalarySendRangeService getSalarySendRangeService(User user) {
        return ServiceUtil.getService(SalarySendRangeServiceImpl.class, user);
    }

    private ExtEmpService getExtEmpService(User user) {
        return ServiceUtil.getService(ExtEmpServiceImpl.class, user);
    }


    private SalaryTemplateService getSalaryTemplateService(User user) {
        return ServiceUtil.getService(SalaryTemplateServiceImpl.class, user);
    }

    private SalarySendMapper getSalarySendMapper() {
        return SqlProxyHandle.getProxy(SalarySendMapper.class);
    }


    private SalaryAcctRecordService getSalaryAcctRecordService(User user) {
        return ServiceUtil.getService(SalaryAcctRecordServiceImpl.class, user);
    }

    private SalaryAcctRecordMapper getSalaryAcctRecordMapper() {
        return SqlProxyHandle.getProxy(SalaryAcctRecordMapper.class);
    }

    private SalaryAcctEmployeeMapper getSalaryAcctEmployeeMapper() {
        return SqlProxyHandle.getProxy(SalaryAcctEmployeeMapper.class);
    }

    //
    private SalaryAcctResultMapper getSalaryAcctResultMapper() {
        return SqlProxyHandle.getProxy(SalaryAcctResultMapper.class);
    }

    private TaxAgentService getTaxAgentService(User user) {
        return ServiceUtil.getService(TaxAgentServiceImpl.class, user);
    }

    private SalaryEmployeeService getSalaryEmployeeService(User user) {
        return ServiceUtil.getService(SalaryEmployeeServiceImpl.class, user);
    }

    private SalarySendInfoMapper getSalarySendInfoMapper() {
        return SqlProxyHandle.getProxy(SalarySendInfoMapper.class);
    }

    private SalarySysConfService getSalarySysConfService(User user) {
        return ServiceUtil.getService(SalarySysConfServiceImpl.class, user);
    }

    private SalarySobService getSalarySobService(User user) {
        return ServiceUtil.getService(SalarySobServiceImpl.class, user);
    }

    private SalaryItemService getSalaryItemService(User user) {
        return ServiceUtil.getService(SalaryItemServiceImpl.class, user);
    }

    private SalaryAcctEmployeeService getSalaryAcctEmployeeService(User user) {
        return ServiceUtil.getService(SalaryAcctEmployeeServiceImpl.class, user);
    }

    private SalaryAcctResultService getSalaryAcctResultService(User user) {
        return ServiceUtil.getService(SalaryAcctResultServiceImpl.class, user);
    }

    private SalarySobItemService getSalarySobItemService(User user) {
        return ServiceUtil.getService(SalarySobItemServiceImpl.class, user);
    }

    private SalarySobBackItemService getSalarySobBackItemService(User user) {
        return ServiceUtil.getService(SalarySobBackItemServiceImpl.class, user);
    }


    private SalaryBillService getSalaryBillService(User user) {
        return ServiceUtil.getService(SalaryBillServiceImpl.class, user);
    }

    private SalaryCacheService getSalaryCacheService(User user) {
        return ServiceUtil.getService(SalaryCacheServiceImpl.class, user);
    }

    private SalaryBillBaseSetService getSalaryBillBaseSetService(User user) {
        return ServiceUtil.getService(SalaryBillBaseSetServiceImpl.class, user);
    }

    public AuthService getAuthService(User user) {
        return ServiceUtil.getService(AuthServiceImpl.class, user);
    }

    private SQLMapper getSQLMapper() {
        return MapperProxyFactory.getProxy(SQLMapper.class);
    }

    @Override
    public SalarySendPO getById(Long salarySendId) {
        return mapper.getById(salarySendId);
    }

    @Override
    public List<Long> findReAccountingIdsByAcctIds(List<Long> salaryAccountingIds, String tenantKey) {
        // 校验salaryAccountingId
        if (CollectionUtils.isEmpty(salaryAccountingIds)) {
            throw new SalaryRunTimeException(SalaryI18nUtil.getI18nLabel(100497, "核算id必传"));
        }

//        List<SalarySendPO> salarySends = new LambdaQueryChainWrapper<>(mapper)
//            .eq(SalarySendPO::getDeleteType, 0)
//            .eq(SalarySendPO::getSendNum, 0)
//            .in(SalarySendPO::getSalaryAccountingId, salaryAccountingIds)
//            .list();

        Map<String, Object> condition = new HashMap<>();
        condition.put("deleteType", 0);
        condition.put("sendNum", 0);
        condition.put("salaryAccountingIds", salaryAccountingIds);
        List<SalarySendPO> salarySends = mapper.listSomeWithCondition(condition);

        return salarySends.stream().map(SalarySendPO::getSalaryAccountingId).collect(Collectors.toList());
    }

    @Override
    public String generateSalaryBill(Long salaryAccountingId) {
        // 校验salaryAccountingId
        if (salaryAccountingId == null) {
            throw new SalaryRunTimeException(SalaryI18nUtil.getI18nLabel(100497, "核算id必传"));
        }

        SalaryAcctRecordPO recordPo = new SalaryAcctRecordPO();
        recordPo.setDeleteType(0);
        recordPo.setId(salaryAccountingId);
        recordPo.setStatus(SalaryAcctRecordStatusEnum.ARCHIVED.getValue());
        List<SalaryAcctRecordPO> acctRecords = getSalaryAcctRecordMapper().listSome(recordPo);
        // 检查核算的归档记录
        if (CollectionUtils.isEmpty(acctRecords)) {
            throw new SalaryRunTimeException(SalaryI18nUtil.getI18nLabel(100498, "核算记录不存在"));
        }


        List<SalaryAcctEmployeePO> salaryAcctEmployees = getSalaryAcctEmployeeService(user).listBySalaryAcctRecordIds(Collections.singletonList(salaryAccountingId));
//        // 根据人员id去重
//        salaryAcctEmployees = salaryAcctEmployees.stream()
//                .collect(Collectors.collectingAndThen(Collectors.toCollection(() -> new TreeSet<>(Comparator.comparing(SalaryAcctEmployeePO::getEmployeeId))), ArrayList::new));

        // 1.回算：如果是回算，todo 要等原始核算发完或者上一个回算记录发完才行，目前最多只有一个回算记录
        if (Objects.equals(acctRecords.get(0).getBackCalcStatus(), (NumberUtils.INTEGER_ONE))) {
            SalarySendPO sendPO = new SalarySendPO();
            sendPO.setDeleteType(0);
            sendPO.setSalaryAccountingId(salaryAccountingId);
            List<SalarySendPO> salarySends = mapper.listSome(sendPO);
            if (CollectionUtils.isEmpty(salarySends)) {
                throw new SalaryRunTimeException(SalaryI18nUtil.getI18nLabel(139700, "还没有正常核算，不能进行回算"));
            }
            // todo 一般只有一个未冻结,同一个原始核算id，后面如果有多级回算，就会换成不同核算id
            SalarySendPO salarySend = salarySends.stream().filter(s -> s.getSendStatus().equals(NumberUtils.INTEGER_ZERO)).findFirst().orElse(null);

            // 根据工资单补发模板规则过滤人员,已发补发的合计项目不等于0的进行过滤
            SalarySobPO salarySob = getSalarySobService(user).getById(acctRecords.get(0).getSalarySobId());
            List<SalaryTemplatePO> salaryTemplates = getSalaryTemplateService(user)
                    .getDefaultTemplates(Arrays.asList(acctRecords.get(0).getSalarySobId(), salarySob == null ? 0L : salarySob.getId()));
            // 如果有默认模板，且规则不是全部
            if (CollectionUtils.isNotEmpty(salaryTemplates) && StringUtils.isEmpty(salaryTemplates.get(0).getReplenishRule())) {
                throw new SalaryRunTimeException(SalaryI18nUtil.getI18nLabel(140649, "请维护补发工资单模板信息"));
            }
            Long backCalcItemId =
                    CollectionUtils.isNotEmpty(salaryTemplates) && !SalaryTemplateReplenishRuleEnum.ALL.getValue().equals(salaryTemplates.get(0).getReplenishRule()) ? Long
                            .parseLong(salaryTemplates.get(0).getReplenishRule()) : 0L;
            SalaryItemPO salaryItem = getSalaryItemService(user).getById(backCalcItemId);
            if (salaryItem != null && salaryItem.getDataType().equals(SalaryArchiveFieldTypeEnum.NUMBER.getValue())) {
                // 目前是单个薪资项目
                List<SalaryAcctResultPO> salaryAcctResultValues = getSalaryAcctResultService(user).listBySalaryAcctRecordIdsAndSalaryItemIds(Collections.singleton(salaryAccountingId), Collections.singleton(backCalcItemId));
                Set<Long> salaryAcctEmployeeIds = salaryAcctResultValues.stream()
                        .filter(salaryAcctResultPO -> SalaryEntityUtil.empty2Zero(salaryAcctResultPO.getResultValue()).compareTo(BigDecimal.ZERO)
                                != 0)
                        .map(SalaryAcctResultPO::getSalaryAcctEmpId)
                        .collect(Collectors.toSet());
                salaryAcctEmployees = salaryAcctEmployees.stream().filter(se -> salaryAcctEmployeeIds.contains(se.getId())).collect(Collectors.toList());
            }

            // 将未冻结的给冻结掉
            if (salarySend != null) {
                salarySend.setSendStatus(NumberUtils.INTEGER_ONE);
                mapper.updateById(salarySend);
            }
            // 2.正常核算
        } else {
            SalarySendPO sendPO = new SalarySendPO();
            sendPO.setDeleteType(0);
            sendPO.setSalaryAccountingId(salaryAccountingId);
            List<SalarySendPO> salarySends = mapper.listSome(sendPO);
            if (CollectionUtils.isNotEmpty(salarySends)) {
                throw new SalaryRunTimeException(SalaryI18nUtil.getI18nLabel(100499, "工资单已生成过，不可再重复生成"));
            }
        }


        Long salarySendId = IdGenerator.generate();
        SalaryAcctRecordPO salaryAcctRecord = acctRecords.get(0);
        // 工资单模板类型：0，正常核算，1是回算，todo 后续可能还有别的算，需要改这里
        Integer templateType = salaryAcctRecord.getBackCalcStatus();

        // 构建工资单发放数据
        List<SalarySendInfoPO> salarySendInfos = salaryAcctEmployees.stream().map(m ->
                SalarySendInfoPO.builder()
                        .id(IdGenerator.generate())
                        .salarySendId(salarySendId)
                        .employeeId(m.getEmployeeId())
                        .employeeType(m.getEmployeeType())
                        .salaryMonth(m.getSalaryMonth())
                        .taxAgentId(m.getTaxAgentId())
                        .salaryAcctRecordId(m.getSalaryAcctRecordId())
                        .sendStatus(SalarySendStatusEnum.UNSEND.getValue())
                        .salaryTemplate("")
                        .salaryAcctType(templateType)
                        .creator((long) user.getUID())
                        .createTime(new Date())
                        .updateTime(new Date())
                        .build()
        ).collect(Collectors.toList());

        SalarySendPO salarySend = SalarySendPO.builder()
                .id(salarySendId)
                .salaryMonth(salaryAcctRecord.getSalaryMonth())
                .salaryAccountingId(salaryAccountingId)
                .salarySobId(salaryAcctRecord.getSalarySobId())
                .sendNum(0)
                .sendTotal(salarySendInfos.size())
                .sendStatus(NumberUtils.INTEGER_ZERO) // 未冻结
                .salaryAcctType(templateType)
                .lastSendTime(new Date())
                .creator((long) user.getUID())
                .createTime(new Date())
                .updateTime(new Date())
                .build();
        // 插入工资单发放记录
        mapper.insert(salarySend);
        // 插入工资单人员记录
        salarySendInfoMapper.batchInsert(salarySendInfos);

        return "";
    }

    @Override
    public void revokeSalaryBill(SalaryAcctRecordPO salaryAcctRecord) {
        // 校验salaryAccountingId
//        if (salaryAcctRecord == null) {
//            throw new SalaryRunTimeException(SalaryI18nUtil.getI18nLabel(100497, "核算id必传"));
//        }
//
//        SalaryAcctRecordPO po = SalaryAcctRecordPO.builder().id(salaryAccountingId).build();
//        List<SalaryAcctRecordPO> acctRecords = getSalaryAcctRecordMapper().listSome(po);
        // 检查核算的归档记录
        if (salaryAcctRecord == null) {
            throw new SalaryRunTimeException(SalaryI18nUtil.getI18nLabel(100498, "核算记录不存在"));
        }

        SalarySendPO build = SalarySendPO.builder().salaryAccountingId(salaryAcctRecord.getId()).deleteType(0).build();
        List<SalarySendPO> salarySends = getSalarySendMapper().listSome(build);
        if (CollectionUtils.isEmpty(salarySends)) {
            return;
//            throw new SalaryRunTimeException(SalaryI18nUtil.getI18nLabel(100505, "工资单不存在"));
        }

        SalarySendPO salarySend;
        if (salaryAcctRecord.getBackCalcStatus() != null && salaryAcctRecord.getBackCalcStatus().equals(NumberUtils.INTEGER_ONE)) {
            // todo 一般只有一个未冻结,同一个原始核算id，后面如果有多级回算，就会换成不同核算id
            salarySend = salarySends.stream().filter(s -> s.getSendStatus().equals(NumberUtils.INTEGER_ZERO)).findFirst().orElse(null);
//            if (salarySend != null && salarySend.getSendNum() < salarySend.getSendTotal()) {
//                throw new SalaryRunTimeException(SalaryI18nUtil.getI18nLabel(139702, "正常原始核算或上一个回算尚未全部发送完成，不能进行撤回"));
//            }
            if (salarySend != null) {
                // 将上一版本未冻结的给冻结掉
                salarySend.setSendStatus(NumberUtils.INTEGER_ONE);
                getSalarySendMapper().updateIgnoreNull(salarySend);
            }
            // 已经回算的删除 todo 以后多级回算注意要修改
            salarySend = salarySends.stream().filter(s -> s.getSalaryAcctType() != null && s.getSalaryAcctType().equals(NumberUtils.INTEGER_ONE)).findFirst().orElse(null);
            handleDelSalaryBill(salarySend);
            // 2.正常核算处理
        } else {
            salarySend = salarySends.stream().filter(s -> s.getSendStatus().equals(NumberUtils.INTEGER_ZERO)).findFirst().orElse(null);
            handleDelSalaryBill(salarySend);
        }
    }


    private void handleDelSalaryBill(SalarySendPO salarySend) {
        if (salarySend == null) {
            return;
        }
        // 已发送工资单
        SalarySendInfoPO sendInfo = SalarySendInfoPO.builder()
                .deleteType(0)
                .salarySendId(salarySend.getId())
                .sendStatus(SalarySendStatusEnum.ALREADYSEND.getValue())
                .salaryAcctType(NumberUtils.INTEGER_ZERO).build();
        List<SalarySendInfoPO> salarySendInfos = getSalarySendInfoMapper().listSome(sendInfo);
        if ((salarySend.getSendNum() > 0 || CollectionUtils.isNotEmpty(salarySendInfos)) && Objects.equals(salarySend.getSalaryAcctType(), NumberUtils.INTEGER_ONE)) {
            throw new SalaryRunTimeException(SalaryI18nUtil.getI18nLabel(100507, "补发工资单已经发放，不可进行回算"));
        } else if (salarySend.getSendNum() > 0 || CollectionUtils.isNotEmpty(salarySendInfos)) {
            throw new SalaryRunTimeException(SalaryI18nUtil.getI18nLabel(100507, "工资单已经发放，不可撤销,可以进行回算"));
        }
        // 删除工资单
        SalarySendPO salarySendPO = SalarySendPO.builder()
                .id(salarySend.getId())
                .sendNum(0)
                .deleteType(1)
                .build();
        getSalarySendMapper().updateIgnoreNull(salarySendPO);
        // 删除工资单发放
        SalarySendInfoPO infoPO = SalarySendInfoPO.builder()
                .salarySendId(salarySend.getId())
                .deleteType(1)
                .build();
        getSalarySendInfoMapper().updateGrantWithdraw(infoPO, salarySend.getId(), null, null);

    }

    @Override
    public PageInfo<SalarySendListDTO> listPage(SalarySendQueryParam queryParam) {
        List<SalarySendListDTO> list = mapper.list(queryParam);
        PageInfo<SalarySendListDTO> pageInfo = new PageInfo<>(list, SalarySendListDTO.class);
        return pageInfo;
    }

    @Override
    public List<SalarySendListDTO> list(SalarySendQueryParam queryParam) {
        List<SalarySendListDTO> list = mapper.list(queryParam);
        return getAuthService(user).auth(list, queryParam.getFilterType(), SalarySendListDTO.class);
    }

    @Override
    public SalarySendBaseInfoDTO getBaseInfo(Long id) {
        SalarySendPO salarySend = mapper.getById(id);
        if (salarySend == null) {
            throw new SalaryRunTimeException(SalaryI18nUtil.getI18nLabel(100512, "工资单发放不存在"));
        }
        Long salaryAcctId = salarySend.getSalaryAccountingId();
        SalarySobCycleDTO salarySobCycleDTO = getSalaryAcctRecordService(user).getSalarySobCycleById(salaryAcctId);
        String template = "";
        // 获取默认模板
        List<SalaryTemplatePO> salaryTemplates = getSalaryTemplateService(user).getDefaultTemplates(Collections.singletonList(salarySend.getSalarySobId()));
        if (CollectionUtils.isNotEmpty(salaryTemplates)) {
            // 是否是回算
            if (Objects.equals(salarySend.getSalaryAcctType(), NumberUtils.INTEGER_ONE)) {
                template = salaryTemplates.get(0).getReplenishName();
            } else {
                template = salaryTemplates.get(0).getName();
            }
        }

        Boolean canSend = true;
        SalaryAcctRecordPO acctRecord = getSalaryAcctRecordService(user).getById(salaryAcctId);
        // 回算过，但是查看的是普通工资单（不能发）;回算过，但是查看的是回算工资单（可以发）；记录没回算过（可以发）
        if (Objects.equals(acctRecord.getBackCalcStatus(), NumberUtils.INTEGER_ONE) && Objects.equals(salarySend.getSalaryAcctType(), NumberUtils.INTEGER_ZERO)) {
            canSend = false;
        }

        return SalarySendBaseInfoDTO.builder()
                .salaryMonth(salarySobCycleDTO.getSalaryMonth())
                .template(template)
                .salarySobCycle(salarySobCycleDTO)
                .sendNum(salarySend.getSendNum())
                .sendTotal(salarySend.getSendTotal())
                .canSend(canSend)
                .showPdfBtn(GEN_PDF)
//                .salaryAcctType(salarySend.getSalaryAcctType().toString())
//                .haveBackCalc(haveBackCalc ? NumberUtils.INTEGER_ONE : NumberUtils.INTEGER_ZERO)
                .build();
    }

    @Override
    public Map<String, Object> mySalaryBill(Long salaryInfoId, Long currentEmployeeId) {
        if (salaryInfoId == null) {
            throw new SalaryRunTimeException("工资单id为空！");
        }

        SalarySendInfoPO salarySendInfo = getSalarySendInfoMapper().getById(salaryInfoId);
        if (salarySendInfo == null) {
            throw new SalaryRunTimeException(SalaryI18nUtil.getI18nLabel(100511, "工资单信息不存在"));
        }

        if (!Objects.equals(salarySendInfo.getSendStatus(), 1)) {
            throw new SalaryRunTimeException(SalaryI18nUtil.getI18nLabel(100511, "工资单信息不存在"));
        }

        Long employeeId = salarySendInfo.getEmployeeId();
        if (currentEmployeeId.compareTo(employeeId) != 0) {

            //主账号可以查看次账号工资单
            String sql = "select id from Hrmresource a where belongto= " + currentEmployeeId;
            List<Long> belongtoIds = getSQLMapper().listLong(sql);
            if (CollUtil.isNotEmpty(belongtoIds) && belongtoIds.contains(employeeId)) {
                //切换user
//                this.user = new User(new Integer(employeeId + ""));
            } else {
                throw new SalaryRunTimeException(SalaryI18nUtil.getI18nLabel(100511, "当前账号无法查看此工资单"));
            }
        }

        // 获取默认模板信息
        SalarySendPO salarySendPO = getSalarySendMapper().getById(salarySendInfo.getSalarySendId());
        if (salarySendPO == null) {
            throw new SalaryRunTimeException("工资单记录不存在");
        }

        //工资单查看时效性
        salaryBillViewingLimitSetting salaryBillViewingLimitSetting = getSalaryBillBaseSetService(user).getSalaryBillViewingLimitSetting();
        Date limitMonth = getLimitMonth(salaryBillViewingLimitSetting);
        if (limitMonth != null) {
            if (salaryBillViewingLimitSetting.getMonthType() == MonthTypeEnum.SALARY_DATE) {
                if (limitMonth.after(salarySendInfo.getSalaryMonth())) {
                    throw new SalaryRunTimeException(SalaryI18nUtil.getI18nLabel(100511, "仅能查看" + salaryBillViewingLimitSetting.getLimitMonth() + "月内的工资单"));
                }
            } else {
                if (limitMonth.after(salarySendInfo.getSendTime())) {
                    throw new SalaryRunTimeException(SalaryI18nUtil.getI18nLabel(100511, "仅能查看" + salaryBillViewingLimitSetting.getLimitMonth() + "月内的工资单"));
                }
            }
        }

        // 获取、记录首次查看时间
        Date firstReadingTime = new Date();
        if (salarySendInfo.getFirstReadingTime() == null) {
            salarySendInfo.setFirstReadingTime(firstReadingTime);
            getSalarySendInfoMapper().updateIgnoreNull(salarySendInfo);
        } else {
            // 不是首次查看
            firstReadingTime = salarySendInfo.getFirstReadingTime();
            // 获取首次查看后多少分钟不能查看工资单（0代表一旦查看后无法再次查看，null代表不限制）
            Integer burningAfterReadingMin = getSalaryBillBaseSetService(user).getBurningAfterReadingMin();
            if (burningAfterReadingMin != null) {
                LocalDateTime limitTime = SalaryDateUtil.dateToLocalDateTime(firstReadingTime).plusMinutes(burningAfterReadingMin);
                LocalDateTime now = SalaryDateUtil.dateToLocalDateTime(new Date());
                if (burningAfterReadingMin == 0) {
                    throw new SalaryRunTimeException("无法查看工资单，该工资单仅供查看一次。首次查看时间为：" + SalaryDateUtil.getFormatLocalDateTime(firstReadingTime));
                } else if (limitTime.isBefore(now)) {
                    throw new SalaryRunTimeException("首次查看工资单" + burningAfterReadingMin + "分钟后，无法查看工资单。首次查看时间为：" + SalaryDateUtil.getFormatLocalDateTime(firstReadingTime));
                }
            }
        }

        // 更新查看状态
        if (salarySendInfo.getBillReadStatus() == null || NumberUtils.compare(salarySendInfo.getBillReadStatus(), BillReadStatusEnum.UNREAD.getValue()) == 0) {
            salarySendInfo.setBillReadStatus(BillReadStatusEnum.READED.getValue());
            salarySendInfo.setUpdateTime(new Date());
            getSalarySendInfoMapper().updateIgnoreNull(salarySendInfo);
        }

        List<SalarySendPO> salarySends = mapper.listSome(SalarySendPO.builder().id(salarySendInfo.getSalarySendId()).build());
        if (CollectionUtils.isEmpty(salarySends)) {
            throw new SalaryRunTimeException(SalaryI18nUtil.getI18nLabel(100512, "工资单发放不存在"));
        }
        SalarySendPO salarySend = salarySends.get(0);
        Date salaryMonth = salarySend.getSalaryMonth();
        // 获取模板
        String salaryTemplateContent = salarySendInfo.getSalaryTemplate();
        if (StringUtils.isBlank(salaryTemplateContent)) {
            throw new SalaryRunTimeException(SalaryI18nUtil.getI18nLabel(100513, "没有默认模板，无法查看"));
        }

        SalaryTemplatePO salaryTemplate = buildSalaryTemplateContent(salaryTemplateContent);
        if (StringUtils.isNotBlank(salaryTemplate.getFeedbackUrl()) && StringUtils.isBlank(salaryTemplate.getMobileFeedbackUrl())) {
            // 如果设置了pc反馈地址，没有设置移动端反馈地址，则移动端反馈地址默认等于pc反馈地址
            salaryTemplate.setMobileFeedbackUrl(salaryTemplate.getFeedbackUrl());
        }
        // 判断是否是补发
        boolean isReplenish = NumberUtils.INTEGER_ONE.equals(salarySendInfo.getSalaryAcctType());

//        if (StringUtils.isNotBlank(salaryTemplate.getBackground())) {
//            FileData fileByte = fileDownloadClient.getFileByte(Long.valueOf(salaryTemplate.getBackground()), currentTenantKey);
//            String encode = Base64Encoder.encode(fileByte.getData());
//            salaryTemplate.setBackground(encode);
//        }

//         获取薪资项目数据

        Long salaryAcctRecordId = salarySendInfo.getSalaryAcctRecordId();
        SalaryAcctResultPO build = SalaryAcctResultPO.builder()
                .salaryAcctRecordId(salaryAcctRecordId)
                .taxAgentId(salarySendInfo.getTaxAgentId())
                .employeeId(employeeId)
                .build();
        List<SalaryAcctResultPO> salaryAcctResultPOS = getSalaryAcctResultMapper().listSome(build);
        if (CollUtil.isEmpty(salaryAcctResultPOS)) {
            throw new SalaryRunTimeException("薪资核算结果不存在！");
        }
        encryptUtil.decryptList(salaryAcctResultPOS, SalaryAcctResultPO.class);

        SalaryAcctRecordPO salaryAcctRecordPO = getSalaryAcctRecordService(user).getById(salaryAcctRecordId);
        if (salaryAcctRecordPO == null) {
            throw new SalaryRunTimeException("薪资核算记录不存在！");
        }
        List<Map<String, Object>> salaryAcctResultS = null;

        // 获取奖金、劳务2个项目的薪资项目id
        Long jjSalaryItemId = SalaryEntityUtil.string2Long(baseBean.getPropValue("fddxfsekyy", "jj_salary_item_id"));
        Long lwSalaryItemId = SalaryEntityUtil.string2Long(baseBean.getPropValue("fddxfsekyy", "lw_salary_item_id"));
        // 获取哪些薪资项目能够展开详情
        List<String> canShowDeatilSalaryItemIds = new ArrayList<>();
        canShowDeatilSalaryItemIds.add(jjSalaryItemId.toString());
        canShowDeatilSalaryItemIds.add(lwSalaryItemId.toString());
        RecordSet rs = new RecordSet();
        rs.execute("select xzdx from uf_gzdxspz");
        while (rs.next()) {
            String xzdx = rs.getString("xzdx");
            if (NumberUtils.isCreatable(xzdx)) {
                canShowDeatilSalaryItemIds.add(xzdx);
            }
        }
        if (Objects.equals(salaryAcctRecordPO.getBackCalcStatus(), NumberUtils.INTEGER_ONE) && !isReplenish) {
            // 该记录回算过,并且获取的不是回算后的工资单
            salaryAcctResultS = salaryAcctResultPOS.stream().map(m -> {
                Map<String, Object> map = new LinkedHashMap<>();
                map.put("salaryItemId", m.getSalaryItemId());
                map.put("resultValue", m.getOriginResultValue());
                return map;
            }).collect(Collectors.toList());
        } else {
            salaryAcctResultS = salaryAcctResultPOS.stream().map(m -> {
                Map<String, Object> map = new LinkedHashMap<>();
                map.put("salaryItemId", m.getSalaryItemId());
                map.put("resultValue", m.getResultValue());
                return map;
            }).collect(Collectors.toList());
        }


        Map<String, Object> map = new LinkedHashMap<>();
        map.put("tenantName", "");
        map.put("sendTime", SalaryDateUtil.getFormatLocalDateTime(salarySendInfo.getSendTime()));
        List<SalaryTemplateSalaryItemSetListDTO> listDTOS = JSONArray.parseArray(isReplenish ? salaryTemplate.getReplenishSalaryItemSetting() : salaryTemplate.getSalaryItemSetting(), SalaryTemplateSalaryItemSetListDTO.class);
        Optional<SalaryTemplateSalaryItemSetListDTO> optionalEmployeeInformation = listDTOS.stream().filter(e -> SalaryTemplateSalaryItemSetGroupConstant.EMPLOYEE_INFO_GROUP_ID.equals(e.getGroupId())).findFirst();
        SalaryTemplateSalaryItemSetListDTO employeeInformation = optionalEmployeeInformation.orElse(null);
        List<SalaryTemplateSalaryItemSetListDTO> itemSetListDTOS = listDTOS.stream().filter(e -> !SalaryTemplateSalaryItemSetGroupConstant.EMPLOYEE_INFO_GROUP_ID.equals(e.getGroupId())).collect(Collectors.toList());
        List<Map<String, Object>> finalSalaryAcctResultS = salaryAcctResultS;
        itemSetListDTOS.stream().forEach(item -> {
            item.getItems()
                    .forEach(e -> {
                        if (CollectionUtils.isEmpty(finalSalaryAcctResultS)) {
                            e.setSalaryItemValue("");
                        } else {
                            Object o = finalSalaryAcctResultS.stream()
                                    .filter(f -> f.get("salaryItemId") != null && String.valueOf(f.get("salaryItemId")).equals(e.getSalaryItemId())).findFirst()
                                    .orElse(new HashMap<>())
                                    .get("resultValue");
                            e.setSalaryItemValue(o == null ? "" : (String) o);
                        }
                        e.setCanShowDetail(canShowDeatilSalaryItemIds.contains(e.getSalaryItemId()));
                    });
        });


        /**
         * 过滤空
         */
        if (Objects.equals(1, salaryTemplate.getSalaryItemNullStatus())) {
            for (SalaryTemplateSalaryItemSetListDTO itemSetListDTO : itemSetListDTOS) {
                List<SalaryTemplateSalaryItemListDTO> items = itemSetListDTO.getItems();
                List<SalaryTemplateSalaryItemListDTO> collect = items.stream().filter(item -> StringUtils.isNotBlank(item.getSalaryItemValue())).collect(Collectors.toList());
                itemSetListDTO.setItems(collect);
            }
        }

        /**
         * 过滤0
         */
        if (Objects.equals(1, salaryTemplate.getSalaryItemZeroStatus())) {
            for (SalaryTemplateSalaryItemSetListDTO itemSetListDTO : itemSetListDTOS) {
                List<SalaryTemplateSalaryItemListDTO> items = itemSetListDTO.getItems();
                List<SalaryTemplateSalaryItemListDTO> collect = items.stream()
                        .filter(item -> SalaryEntityUtil.string2BigDecimal(item.getSalaryItemValue()) == null
                                || BigDecimal.ZERO.compareTo(SalaryEntityUtil.string2BigDecimal(item.getSalaryItemValue())) != 0)
                        .collect(Collectors.toList());
                itemSetListDTO.setItems(collect);
            }
        }

        DataCollectionEmployee simpleEmployee = getSalaryEmployeeService(user).getEmployeeById(employeeId);
        SalaryAcctEmployeePO acctEmployeePO = getSalaryAcctEmployeeService(user).getById(salaryAcctResultPOS.get(0).getSalaryAcctEmpId());
        SalaryAcctEmployeeBO.copyAcctEmp(simpleEmployee, acctEmployeePO);
        TaxAgentPO taxAgentPO = getTaxAgentService(user).getById(acctEmployeePO.getTaxAgentId());
        buildEmployeeInfo(employeeInformation, simpleEmployee, taxAgentPO.getName(), SalaryAcctResultBO.buildEmployeeFieldName());
        map.put("employeeInformation", employeeInformation);
        map.put("salaryGroups", itemSetListDTOS);

        salaryTemplate.setTheme(getBillTitle(salaryTemplate.getTheme(), salaryMonth, employeeId));
        // 工资单水印文本型动态变量 == 处理
        handleSalaryWatermark(salaryTemplate, salarySendInfo, employeeId);
        map.put("salaryTemplate", salaryTemplate);
        map.put("salaryAcctResult", salaryAcctResultS);

        // 工资单确认按钮
        if (NumberUtils.compare(salaryTemplate.getAckFeedbackStatus(), 1) == 0) {
            // 开启了工资单确认
            Integer ackStatus = salarySendInfo.getBillConfirmStatus();
            if (ackStatus == null || ackStatus != BillConfimStatusEnum.CONFIRMED.getValue()) {
                map.put("showAck", "1");
            } else {
                map.put("showAck", "0");
            }
            map.put("sendEmployeeId", salarySendInfo.getSendEmployeeId());
        } else {
            map.put("showAck", "0");
        }


        // 工资单反馈
        if (NumberUtils.compare(salaryTemplate.getFeedbackStatus(), 1) == 0) {
            // 开启了工资单反馈按钮
            Integer confirmStatus = salarySendInfo.getBillConfirmStatus();
            map.put("showFeedback", "1");
            // 除非确认状态为已确认否则可以一直反馈
            if (NumberUtils.compare(salaryTemplate.getAckFeedbackStatus(), 1) == 0 && confirmStatus != null && confirmStatus == BillConfimStatusEnum.CONFIRMED.getValue()) {
                map.put("showFeedback", "0");
            }
            map.put("sendEmployeeId", salarySendInfo.getSendEmployeeId());
        } else {
            map.put("showFeedback", "0");
        }

        // 获取奖金、劳务2个项目的薪资项目id
        map.put("jjSalaryItemId", jjSalaryItemId);
        map.put("lwSalaryItemId", lwSalaryItemId);

        // 记录查看日志
        String targetName = taxAgentPO.getName() + "-" + SalaryDateUtil.getFormatYearMonth(salarySendInfo.getSalaryMonth());
        LoggerContext<SalaryItemPO> loggerContext = new LoggerContext<>();
        loggerContext.setUser(user);
        loggerContext.setTargetId(String.valueOf(salarySendInfo.getId()));
        loggerContext.setTargetName(targetName);
        loggerContext.setOperateType(OperateTypeEnum.READ.getValue());
        loggerContext.setOperateTypeName(SalaryI18nUtil.getI18nLabel(0, "查看工资单") + ": " + targetName + " " + salaryInfoId);
        loggerContext.setOperatedesc(SalaryI18nUtil.getI18nLabel(0, "查看工资单") + ": " + targetName);
        SalaryElogConfig.mySalaryBillLoggerTemplate.write(loggerContext);
        return map;
    }


    //工资单时效性
    private Date getLimitMonth(salaryBillViewingLimitSetting salaryBillViewingLimitSetting) {
        if (salaryBillViewingLimitSetting.getLimitMonth() != 0) {
            MonthTypeEnum monthType = salaryBillViewingLimitSetting.getMonthType();
            LocalDate localDate;
            if (monthType == MonthTypeEnum.SALARY_DATE) {
                localDate = LocalDate.now().plusMonths(-salaryBillViewingLimitSetting.getLimitMonth() + 1).withDayOfMonth(1);
            } else {
                localDate = LocalDate.now().plusMonths(-salaryBillViewingLimitSetting.getLimitMonth());
            }
            return SalaryDateUtil.localDateToDate(localDate);
        }
        return null;
    }


    /**
     * 工资单水印文本型动态变量 == 处理
     *
     * @param salaryTemplate
     * @param salarySendInfo
     */
    private void handleSalaryWatermark(SalaryTemplatePO salaryTemplate, SalarySendInfoPO salarySendInfo, Long employeeId) {
        SalaryBillWatermarkDTO salaryBillWatermark = JsonUtil.parseObject(salaryTemplate.getSalaryWatermark(), SalaryBillWatermarkDTO.class);
        if (Objects.isNull(salaryBillWatermark) || Boolean.FALSE.equals(salaryBillWatermark.getWatermarkStatus())) {
            return;
        }
        // 发送时已经处理好变量字段，可直接获取判断
        List<String> wmTextFieldIds = (List<String>) salaryBillWatermark.getWmSetting().getOrDefault("wmSelectedFieldIds", Lists.newArrayList());
        if (CollectionUtils.isEmpty(wmTextFieldIds)) {
            return;
        }

        boolean needQueryEmp = (boolean) salaryBillWatermark.getWmSetting().getOrDefault("needQueryEmp", false);
        DataCollectionEmployee simpleEmployee = null;
        if (needQueryEmp) {
            simpleEmployee = getSalaryEmployeeService(user).getEmployeeById(employeeId);
        }

        String wmText = salaryBillWatermark.getWmSetting().getOrDefault("wmText", StringUtils.EMPTY).toString();
        wmText = SalaryBillBO.handleWmText(wmText, wmTextFieldIds, simpleEmployee);

        // 重新设回水印
        salaryBillWatermark.getWmSetting().put("wmText", wmText);
        salaryTemplate.setSalaryWatermark(JSON.toJSONString(salaryBillWatermark));
    }


    @Override
    public PageInfo<SalarySendInfoListDTO> salarySendInfoListPage(SalarySendInfoQueryParam queryParam) {

        //排序配置
        OrderRuleVO orderRule = getSalarySysConfService(user).orderRule();
        queryParam.setOrderRule(orderRule);

        List<SalarySendInfoListDTO> page = salarySendInfoMapper.list(queryParam);
        PageInfo<SalarySendInfoListDTO> pageInfo = SalaryPageUtil.buildPage(queryParam.getCurrent(), queryParam.getPageSize(),
                page, SalarySendInfoListDTO.class);
        SalarySendPO salarySendPO = getSalarySendMapper().getById(queryParam.getSalarySendId());
        if (salarySendPO == null) {
            throw new SalaryRunTimeException("工资单不存在或已被删除");
        }
        List<SalaryTemplatePO> salaryTemplates = getSalaryTemplateService(user).getDefaultTemplates(Collections.singletonList(salarySendPO.getSalarySobId()));
        if (CollectionUtils.isEmpty(salaryTemplates)) {
            throw new SalaryRunTimeException("请先设置工资单模板");
        }
        Integer ackFeedbackStatus = salaryTemplates.get(0).getAckFeedbackStatus();
        Integer feedbackStatus = salaryTemplates.get(0).getFeedbackStatus();
        if ((ackFeedbackStatus != null && NumberUtils.compare(ackFeedbackStatus, 1) == 0)
                || (feedbackStatus != null && NumberUtils.compare(feedbackStatus, 1) == 0)) {
            // 默认为空时，未读未确认
            pageInfo.getList().stream().forEach(obj -> {
                SalarySendInfoListDTO dto = (SalarySendInfoListDTO) obj;
                if (StringUtils.isBlank(dto.getBillReadStatus())) {
                    dto.setBillReadStatus(BillReadStatusEnum.UNREAD.getDefaultLabel());
                } else {
                    dto.setBillReadStatus(BillReadStatusEnum.getDefaultLabelByValue(Integer.valueOf(dto.getBillReadStatus())));
                }
                if (StringUtils.isBlank(dto.getBillConfirmStatus())) {
                    dto.setBillConfirmStatus(BillConfimStatusEnum.UNCONFIRMED.getDefaultLabel());
                } else {
                    dto.setBillConfirmStatus(BillConfimStatusEnum.getDefaultLabelByValue(Integer.valueOf(dto.getBillConfirmStatus())));
                }
            });
        }
        List<SalarySendInfoListDTO> list = pageInfo.getList();

        List<DataCollectionEmployee> employeeList = getExtEmpService(user).listEmployee();
        Map<Long, DataCollectionEmployee> employeeMap = SalaryEntityUtil.convert2Map(employeeList, DataCollectionEmployee::getEmployeeId);
        list.forEach(dto -> {
            if (dto.getEmployeeType() != null && Objects.equals(dto.getEmployeeType(), 1)) {
                DataCollectionEmployee employee = employeeMap.get(dto.getEmployeeId());
                dto.setUsername(employee.getUsername());
                dto.setDepartment(employee.getDepartmentName());
                dto.setMobile(employee.getMobile());
                dto.setJobNum(employee.getWorkcode());
            }
        });
        pageInfo.setList(list);
        return pageInfo;
    }

    @Override
    public List<SalarySendInfoListDTO> salarySendInfoList(Long salarySendId) {
        return salarySendInfoMapper.list(SalarySendInfoQueryParam.builder().salarySendId(salarySendId).build());
    }

    @Override
    public PageInfo<SalaryMySalaryBillListDTO> mySalaryBillListPage(SalaryBillQueryParam queryParam) {
        List<SalaryMySalaryBillListDTO> list = salarySendInfoMapper.mySalaryBillList(queryParam);

        //工资单时效
        salaryBillViewingLimitSetting salaryBillViewingLimitSetting = getSalaryBillBaseSetService(user).getSalaryBillViewingLimitSetting();
        Date limitMonth = getLimitMonth(salaryBillViewingLimitSetting);
        list = list.stream()
                .filter(dto -> {
                    if (limitMonth != null) {
                        if (salaryBillViewingLimitSetting.getMonthType() == MonthTypeEnum.SALARY_DATE) {
                            return !limitMonth.after(dto.getSalaryYearMonth());
                        } else {
                            return !limitMonth.after(dto.getSendTime());
                        }
                    }
                    return true;
                })
                .peek(dto -> {
                    dto.setEmployeeId((long) user.getUID());
                    dto.setSalaryCode(AESEncryptUtil.encrypt4SalaryBill(String.valueOf(user.getUID())));
                })
                .collect(Collectors.toList());

        PageInfo<SalaryMySalaryBillListDTO> pageInfo = new PageInfo<>(list, SalaryMySalaryBillListDTO.class);
        pageInfo.setTotal(list.size());
        pageInfo.setPageNum(queryParam.getCurrent());
        pageInfo.setPageSize(queryParam.getPageSize());
        return pageInfo;
    }

    @Override
    public PageInfo<SalarySendDetailListDTO> salarySendInfoDetailListPage(SalarySendDetailQueryParam queryParam) {
        //排序配置
        OrderRuleVO orderRule = getSalarySysConfService(user).orderRule();
        queryParam.setOrderRule(orderRule);
        Set<Long> otherSalaryAcctRecordIds = null;
        if (queryParam.getMergeCountTax() != null && queryParam.getMergeCountTax()) {
            //查询关联的核酸id，这里认为一次发放只对应一条核算记录
            Long salaryAccRecordId = salarySendInfoMapper.listSalaryAccRecordIds(queryParam).stream().findFirst().orElse(null);
            // 查询合并计税的其他薪资核算记录
            List<SalaryAcctRecordPO> otherSalaryAcctRecordPOS = getSalaryAcctRecordService(user).listById4OtherConsolidatedTax(salaryAccRecordId);
            if (CollectionUtils.isEmpty(otherSalaryAcctRecordPOS)) {
                return new PageInfo<>();
            }
            // 查询合并计税的薪资核算人员
            otherSalaryAcctRecordIds = SalaryEntityUtil.properties(otherSalaryAcctRecordPOS, SalaryAcctRecordPO::getId);
            if (otherSalaryAcctRecordIds.isEmpty()) {
                return new PageInfo<>();
            }
        }
        List<SalarySendDetailListDTO> list = getSalarySendInfoMapper().detailList(queryParam, otherSalaryAcctRecordIds);
        return SalaryPageUtil.buildPage(queryParam.getCurrent(), queryParam.getPageSize(), list, SalarySendDetailListDTO.class);
    }

    /**
     * 获取工资单标题
     *
     * @param theme
     * @param salaryMonth
     * @param employeeId
     * @return
     */
    private String getBillTitle(String theme, Date salaryMonth, Long employeeId) {
        String companyName = "";
        if (employeeId != null) {
            ResourceComInfo resourceComInfo = null;
            SubCompanyComInfo subCompanyComInfo = new SubCompanyComInfo();
            try {
                resourceComInfo = new ResourceComInfo();
                subCompanyComInfo = new SubCompanyComInfo();
            } catch (Exception e) {
                log.error("资源异常", e);
            }
            companyName = subCompanyComInfo.getSubCompanyname(resourceComInfo.getSubCompanyID(employeeId + ""));
        }
        return theme.replace("${companyName}", companyName)
                .replace("${salaryMonth}", new SimpleDateFormat("yyyy年MM月").format(salaryMonth))
                .replace("${salaryYear}", new SimpleDateFormat("yyyy年").format(salaryMonth));
    }

    /**
     * 根据模板内容转为工资单模板对象
     * 注意：不要通过反序列化，可能会因为历史原因修改字段名称而无法序列化
     *
     * @param salaryTemplateContent
     * @return
     */
    private SalaryTemplatePO buildSalaryTemplateContent(String salaryTemplateContent) {
        Map<String, Object> map = JsonUtil.parseMap(salaryTemplateContent, Object.class);
        SalaryTemplatePO build = SalaryTemplatePO.builder()
                .id(Long.valueOf(map.getOrDefault("id", "0").toString()))
                .ackFeedbackStatus(Integer.valueOf(map.getOrDefault("ackFeedbackStatus", "0").toString()))
                .autoAckDays(Integer.valueOf(map.getOrDefault("autoAckDays", "0").toString()))
                .feedbackUrl(map.getOrDefault("feedbackUrl", "").toString())
                .mobileFeedbackUrl(map.getOrDefault("mobileFeedbackUrl", "").toString())
                .name(map.getOrDefault("name", "").toString())
                .salarySobId(Long.valueOf(map.getOrDefault("salarySobId", "0").toString()))
                .useType(Integer.valueOf(map.getOrDefault("useType", "0").toString()))
                .description(map.getOrDefault("description", "").toString())
                .emailStatus(Integer.valueOf(map.getOrDefault("emailStatus", "0").toString()))
                .sendEmailId(Long.valueOf(map.getOrDefault("sendEmailId", "0").toString()))
                .msgStatus(Integer.valueOf(map.getOrDefault("msgStatus", "0").toString()))
                .salaryWatermark(map.getOrDefault("salaryWatermark", StringUtils.EMPTY).toString())
                .theme(map.getOrDefault("theme", "").toString())
                .background(map.getOrDefault("background", "").toString())
                .textContent(map.getOrDefault("textContent", "").toString())
                .textContentPosition(Integer.valueOf(map.getOrDefault("textContentPosition", "0").toString()))
                .salaryItemNullStatus(Integer.valueOf(map.getOrDefault("salaryItemNullStatus", "0").toString()))
                .salaryItemZeroStatus(Integer.valueOf(map.getOrDefault("salaryItemZeroStatus", "0").toString()))
                .salaryItemSetting(map.getOrDefault("salaryItemSetting", "").toString())
                .replenishName(map.getOrDefault("replenishName", "").toString())
                .replenishSalaryItemSetting(map.getOrDefault("replenishSalaryItemSetting", "").toString())
                .build();
        Object feedbackStatus = map.get("feedbackStatus");
        if (feedbackStatus == null || StringUtils.isBlank(feedbackStatus.toString())) {
            build.setFeedbackStatus(build.getAckFeedbackStatus());
        } else {
            build.setFeedbackStatus(Integer.valueOf(feedbackStatus.toString()));
        }
        return build;
    }

    /**
     * 构建工资单中的人员信息
     *
     * @param employeeInformation
     * @param simpleEmployee
     * @param taxAgentName
     * @param employeeField
     */
    private void buildEmployeeInfo(SalaryTemplateSalaryItemSetListDTO employeeInformation, DataCollectionEmployee simpleEmployee, String taxAgentName, Map<String, String> employeeField) {
        if (employeeInformation == null) {
            return;
        }
        if (CollectionUtils.isNotEmpty(employeeInformation.getItems())) {
            //获取员工信息的字段名和中文描述的map关系
            SalaryFormulaEmployeeDTO salaryFormulaEmployeeDTO = SalaryFormulaEmployeeDTO.builder().employeeId(simpleEmployee.getEmployeeId())
                    .birthday(simpleEmployee.getBirthday())
                    .departmentId(simpleEmployee.getDepartmentId())
                    .departmentName(simpleEmployee.getDepartmentName())
                    .subcompanyId(simpleEmployee.getSubcompanyid())
                    .subcompanyName(simpleEmployee.getSubcompanyName())
                    .email(StringUtils.isEmpty(simpleEmployee.getEmail()) ? "" : simpleEmployee.getEmail())
                    .jobcall(simpleEmployee.getJobcall())
                    .jobcallId(simpleEmployee.getJobcallId())
                    .companystartdate(simpleEmployee.getCompanystartdate())
                    .sex("0".equals(simpleEmployee.getSex()) ? "男" : "女")
                    .mobile(StringUtils.isEmpty(simpleEmployee.getMobile()) ? "" : simpleEmployee.getMobile())
                    .jobtitleName(simpleEmployee.getJobtitleName())
                    .jobtitleId(simpleEmployee.getJobtitleId())
                    .status(simpleEmployee.getStatus() == null ? "" : simpleEmployee.getStatus())
                    .statusName(simpleEmployee.getStatus() == null ? "" : UserStatusEnum.parseByValue(new Integer(Util.null2s(simpleEmployee.getStatus(), "1"))).getDefaultLabel())
                    .accountTypeName(simpleEmployee.getAccountType() == null ? "" : AccountTypeEnum.parseByValue(simpleEmployee.getAccountType()).getDefaultLabel())
                    .telephone(StringUtils.isEmpty(simpleEmployee.getTelephone()) ? "" : simpleEmployee.getTelephone())
                    .username(StringUtils.isEmpty(simpleEmployee.getUsername()) ? "" : simpleEmployee.getUsername())
                    .workcode(StringUtils.isEmpty(simpleEmployee.getWorkcode()) ? "" : simpleEmployee.getWorkcode())
                    .idNo(Util.null2String(simpleEmployee.getIdNo()))
                    .taxAgentName(taxAgentName)
                    .build();
            List<SalaryTemplateSalaryItemListDTO> items = employeeInformation.getItems();
            Set<Map.Entry<String, String>> entries = employeeField.entrySet();
            for (SalaryTemplateSalaryItemListDTO e : items) {
                Optional<Map.Entry<String, String>> entry = entries.stream().filter(f -> Objects.equals(e.getName(), f.getValue())).findFirst();
                if (entry.isPresent()) {
                    String key = entry.get().getKey();
                    if (StringUtils.isNotBlank(key)) {
                        String getter = "get" + key.substring(0, 1).toUpperCase() + key.substring(1);
                        try {
                            Method method = salaryFormulaEmployeeDTO.getClass().getMethod(getter);
                            Object invoke = method.invoke(salaryFormulaEmployeeDTO);
                            e.setSalaryItemValue(Util.null2String(invoke));
                        } catch (NoSuchMethodException | IllegalAccessException | InvocationTargetException ex) {
                            log.error("no such method", e);
                        }
                    }
                }
            }
        }
    }

    /**
     * 组装详情数据
     *
     * @param salaryItems
     * @param list
     * @param salaryAccountingId
     */
    @Override
    public List<Map<String, Object>> buildDetailList(List<SalaryTemplateSalaryItemListDTO> salaryItems, List<SalarySendDetailListDTO> list, Long salaryAccountingId) {
        List<Map<String, Object>> listMaps = new ArrayList<>();
        if (CollectionUtils.isEmpty(list)) {
            return listMaps;
        }

        SalaryAcctResultPO build = SalaryAcctResultPO.builder()
                .salaryAcctRecordId(salaryAccountingId)
                .employeeIds(list.stream().map(SalarySendDetailListDTO::getEmployeeId).collect(Collectors.toList()))
                .build();
        List<SalaryAcctResultPO> salaryAccountingResults = getSalaryAcctResultMapper().listSome(build);
        encryptUtil.decryptList(salaryAccountingResults, SalaryAcctResultPO.class);
        // 动态列
        List<DataCollectionEmployee> employeeList = getExtEmpService(user).listEmployee();
        Map<Long, List<DataCollectionEmployee>> idEmpMap = SalaryEntityUtil.group2Map(employeeList, DataCollectionEmployee::getEmployeeId);
        list.forEach(e -> {
            //是否外部人员
            Integer employeeType = e.getEmployeeType();
            DataCollectionEmployee extEmp = new DataCollectionEmployee();
            if (Objects.equals(employeeType, 1)) {
                List<DataCollectionEmployee> employees = idEmpMap.getOrDefault(e.getEmployeeId(), new ArrayList<>());
                if (CollectionUtils.isNotEmpty(employees) && employees.size() == 1) {
                    extEmp = employees.get(0);
                }
            }
            Map<String, Object> map = new LinkedHashMap<>();
            map.put("id", e.getId());
            map.put("employeeId", e.getEmployeeId());
            map.put("username", Objects.equals(employeeType, 1) ? extEmp.getUsername() : e.getUsername());
            map.put("taxAgent", e.getTaxAgent());
            map.put("department", Objects.equals(employeeType, 1) ? extEmp.getDepartmentName() : e.getDepartment());
            map.put("mobile", Objects.equals(employeeType, 1) ? extEmp.getMobile() : e.getMobile());
            map.put("jobNum", Objects.equals(employeeType, 1) ? extEmp.getWorkcode() : e.getJobNum());
            map.put("email", Objects.equals(employeeType, 1) ? extEmp.getEmail() : e.getEmail());
            salaryItems.forEach(i -> {
                Optional<SalaryAcctResultPO> optional = salaryAccountingResults.stream()
                        .filter(r -> r.getTaxAgentId().equals(e.getTaxAgentId())
                                && r.getEmployeeId().equals(e.getEmployeeId())
                                && r.getSalaryItemId().equals(Long.valueOf(i.getSalaryItemId())))
                        .findFirst();
                map.put(i.getSalaryItemId() + SalaryItemConstant.DYNAMIC_SUFFIX, optional.isPresent() ? optional.get().getResultValue() : "");
            });

            listMaps.add(map);
        });
        return listMaps;
    }

    /**
     * 组装详情数据
     *
     * @param list
     * @param salaryAcctResultValues
     * @return
     */
    @Override
    public List<Map<String, Object>> buildDetailList(List<SalarySendInfoListDTO> list, List<SalaryAcctEmployeePO> salaryAcctEmployees, List<SalaryAcctResultPO> salaryAcctResultValues) {
        List<Map<String, Object>> listMaps = new ArrayList<>();
        if (CollectionUtils.isEmpty(list)) {
            return listMaps;
        }
        // 所有个税扣缴义务人
        Map<Long, String> taxAgentMap = SalaryEntityUtil.convert2Map(getTaxAgentService(user).listAll(), TaxAgentPO::getId, TaxAgentPO::getName);
        // 按人员分组核算数据
//        Map<Long, List<SalaryAcctEmployeePO>> relationSalaryAcctEmployeeMap = SalaryEntityUtil.group2Map(salaryAcctEmployees, SalaryAcctEmployeePO::getEmployeeId);
        Map<String, SalaryAcctEmployeePO> salaryAcctEmployeeMap = SalaryEntityUtil.convert2Map(salaryAcctEmployees, salaryAcctEmployee -> salaryAcctEmployee.getEmployeeId() + "-" + salaryAcctEmployee.getTaxAgentId());
        Map<Long, List<SalaryAcctResultPO>> singleEmpAcctMap = SalaryEntityUtil.group2Map(salaryAcctResultValues, SalaryAcctResultPO::getSalaryAcctEmpId);
        List<Long> employeeIds = list.stream().map(SalarySendInfoListDTO::getEmployeeId).distinct().collect(Collectors.toList());
        // 查询分部 查询部门
        List<DataCollectionEmployee> comInfos = getSalaryEmployeeService(user).getEmployeeByIdsAll(employeeIds);
        Map<Long, DataCollectionEmployee> empComMap = SalaryEntityUtil.convert2Map(comInfos, DataCollectionEmployee::getEmployeeId);

        // 当前查询核算记录是回算，但是发放不是回算的，那么就取源头核算结果
        boolean isOrigin = false;
        SalaryAcctRecordPO salaryAcctRecord = CollectionUtils.isEmpty(list) ? null : getSalaryAcctRecordService(user).getById(list.get(0).getSalaryAcctRecordId());
        if (Objects.nonNull(salaryAcctRecord) && Objects.nonNull(salaryAcctRecord.getBackCalcStatus())) {
            isOrigin = !NumberUtils.INTEGER_ONE.equals(list.get(0).getSalaryAcctType()) && salaryAcctRecord.getBackCalcStatus().equals(NumberUtils.INTEGER_ONE);
        }

        // 动态列
        long l = System.currentTimeMillis();
        Map<Long, DataCollectionEmployee> finalEmpSubComMap = empComMap;
        boolean finalIsOrigin = isOrigin;
        list.forEach(e -> {

            SalaryAcctEmployeePO salaryAcctEmployee = salaryAcctEmployeeMap.getOrDefault(e.getEmployeeId() + "-" + e.getTaxAgentId(), new SalaryAcctEmployeePO());
            DataCollectionEmployee hrmDepartmentComInfo = finalEmpSubComMap.getOrDefault(e.getEmployeeId(), new DataCollectionEmployee());
            Map<String, Object> map = new LinkedHashMap<>();
            map.put("id", e.getId());
            map.put("employeeId", e.getEmployeeId());
            map.put("taxAgentName", taxAgentMap.getOrDefault(salaryAcctEmployee.getTaxAgentId(), ""));
            map.put("taxAgentId", salaryAcctEmployee.getTaxAgentId());
            map.put("username", hrmDepartmentComInfo.getUsername());

            map.put("departmentName", salaryAcctEmployee.getDepartmentName());
            map.put("departmentId", salaryAcctEmployee.getDepartmentId());

            map.put("subcompanyName", salaryAcctEmployee.getSubcompanyName());
            map.put("subcompanyId", salaryAcctEmployee.getSubcompanyId());
            map.put("jobtitleName", salaryAcctEmployee.getJobtitleName());
            map.put("jobtitleId", salaryAcctEmployee.getJobtitleId());
            map.put("companystartdate", hrmDepartmentComInfo.getCompanystartdate());
            map.put("mobile", e.getMobile());
            map.put("dissmissdate", hrmDepartmentComInfo.getDismissdate());
            map.put("status", salaryAcctEmployee.getStatus());
            map.put("statusName", UserStatusEnum.getDefaultLabelByValue(NumberUtils.toInt(salaryAcctEmployee.getStatus(), 1)));
            map.put("workcode", hrmDepartmentComInfo.getWorkcode());
            map.put("sex", "0".equals(hrmDepartmentComInfo.getSex()) ? "男" : "女");
            map.put("idNo", hrmDepartmentComInfo.getIdNo());
            map.put("email", hrmDepartmentComInfo.getEmail());
            map.put("telephone", hrmDepartmentComInfo.getTelephone());
            map.put("jobcall", hrmDepartmentComInfo.getJobcall());
            map.put("jobcallId", hrmDepartmentComInfo.getJobcallId());
            map.put("birthday", hrmDepartmentComInfo.getBirthday());

            map.put("accountType", hrmDepartmentComInfo.getAccountType());
            map.put("accountTypeName", hrmDepartmentComInfo.getAccountTypeName());

//            map.put("employeeType", SalarySendEmployeeTypeEnum.getNameByValue(e.getEmployeeType()));
            // 单个人的核算数据
            List<SalaryAcctResultPO> resultValues = Lists.newArrayList();

            if (singleEmpAcctMap.containsKey(salaryAcctEmployee.getId())) {
                resultValues.addAll(singleEmpAcctMap.get(salaryAcctEmployee.getId()));
            }
            // 薪资项目
//            singleEmpAcctResultList.forEach(i-> map.put(i.getSalaryItemId() + SalaryArchiveConstant.DYNAMIC_SUFFIX, i.getResultValue()));
            for (SalaryAcctResultPO salaryAcctResultValue : resultValues) {
                if (finalIsOrigin) {
                    map.put(salaryAcctResultValue.getSalaryItemId() + SalaryArchiveConstant.DYNAMIC_SUFFIX, salaryAcctResultValue.getResultValue());
                } else {
                    map.put(salaryAcctResultValue.getSalaryItemId() + SalaryArchiveConstant.DYNAMIC_SUFFIX, salaryAcctResultValue.getResultValue());
                }
            }

            listMaps.add(map);
        });
        log.info("工资单详情构建总耗时：{}毫秒", System.currentTimeMillis() - l);
        return listMaps;
    }

    /**
     * 通过薪资账套获取模板的薪资项目
     *
     * @param salaryTemplate
     * @return
     */
    @Override
    public List<SalaryTemplateSalaryItemListDTO> getSalaryItemsSetting(SalaryTemplatePO salaryTemplate, boolean isReplenish) {
        List<SalaryTemplateSalaryItemListDTO> salaryItems = new ArrayList<>();

        if (salaryTemplate == null) {
            return salaryItems;
        }
        // 正常模板
        if (!isReplenish && StringUtils.isNotEmpty(salaryTemplate.getSalaryItemSetting())) {
            List<SalaryTemplateSalaryItemSetListDTO> salaryTemplateShowSetData = JsonUtil.parseList(salaryTemplate.getSalaryItemSetting(), SalaryTemplateSalaryItemSetListDTO.class);
            salaryTemplateShowSetData.stream().filter(f -> !f.getGroupId().equals(SalaryTemplateSalaryItemSetGroupConstant.EMPLOYEE_INFO_GROUP_ID)).forEach(e -> {
                salaryItems.addAll(e.getItems());
            });
        }
        // 补发模板
        if (isReplenish && StringUtils.isNotEmpty(salaryTemplate.getReplenishSalaryItemSetting())) {
            List<SalaryTemplateSalaryItemSetListDTO> salaryTemplateShowSetData = JsonUtil.parseList(salaryTemplate.getReplenishSalaryItemSetting(), SalaryTemplateSalaryItemSetListDTO.class);
            salaryTemplateShowSetData.stream().filter(f -> !f.getGroupId().equals(SalaryTemplateSalaryItemSetGroupConstant.EMPLOYEE_INFO_GROUP_ID)).forEach(e -> salaryItems.addAll(e.getItems()));
        }
        return salaryItems;
    }

    @Override
    public Map<String, Object> grant(SalarySendGrantParam param) {
        if (param.getSalarySendId() == null) {
            throw new SalaryRunTimeException(SalaryI18nUtil.getI18nLabel(100327, "工资单发放Id必传"));
        }

        Long salarySendId = param.getSalarySendId();
        Collection<Long> ids = param.getIds();

        if (param.getSalarySendRangeIds() != null) {
            //如果传了范围id，则使用范围id发放
            ids = getSalarySendRangeService(user).getSendInfoIdsBySendId(salarySendId, param.getSalarySendRangeIds(), SalarySendGrantTypeEnum.GRANT);
            if (ids.isEmpty()) {// 由于查出来是空的，会导致全部发放，在此进行拦截
                throw new SalaryRunTimeException("工资发放范围内没有匹配员工");
            }
        }

        SalarySendPO salarySend = mapper.getById(salarySendId);
        if (salarySend == null) {
            throw new SalaryRunTimeException(SalaryI18nUtil.getI18nLabel(100512, "工资单发放不存在"));
        }
        // 已经冻结不能操作
        if (Objects.equals(salarySend.getSendStatus(), NumberUtils.INTEGER_ONE)) {
            throw new SalaryRunTimeException(SalaryI18nUtil.getI18nLabel(0, "工资单已冻结"));
        }
        // 获取默认模板
        List<SalaryTemplatePO> salaryTemplates = getSalaryTemplateService(user).getDefaultTemplates(Collections.singletonList(salarySend.getSalarySobId()));
        if (CollectionUtils.isEmpty(salaryTemplates)) {
            throw new SalaryRunTimeException(SalaryI18nUtil.getI18nLabel(100518, "没有默认模板，无法发送"));
        }
        SalaryTemplatePO templatePO = salaryTemplates.get(0);
        Date sendTime = new Date();
        // 获取可发送的列表
        List<Map<String, Object>> enableSendList = getEnableSendList(salarySend, ids, templatePO);
        // 1.发放

        SalarySendInfoPO po = new SalarySendInfoPO();
        po.setSendStatus(SalarySendStatusEnum.ALREADYSEND.getValue());
        po.setSendTime(sendTime);
        po.setSalaryTemplate(JsonUtil.toJsonString(templatePO));
        po.setBillReadStatus(BillReadStatusEnum.UNREAD.getValue());
        po.setBillConfirmStatus(BillConfimStatusEnum.UNCONFIRMED.getValue());
        po.setSendEmployeeId(Long.valueOf(user.getUID()));
        salarySendInfoMapper.updateGrantWithdraw(po, salarySendId, Arrays.asList(SalarySendStatusEnum.UNSEND.getValue(), SalarySendStatusEnum.WITHDRAW.getValue()), ids);


        List<SalarySendInfoPO> list = salarySendInfoMapper.listSome(SalarySendInfoPO.builder().salarySendId(salarySendId).build());
        //需要发送工资的人

        List<SalarySendInfoPO> sendList = list.stream().filter(e -> e.getSendStatus().equals(SalarySendStatusEnum.ALREADYSEND.getValue())).collect(Collectors.toList());
        int sendNum = sendList.size();
        int sendTotal = list.size();
        SalarySendPO salarySendNew = new SalarySendPO();
        try {
            BeanUtils.copyProperties(salarySendNew, salarySend);
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        } catch (InvocationTargetException e) {
            e.printStackTrace();
        }
        salarySendNew.setSendNum(sendNum);
        salarySendNew.setSendTotal(sendTotal);
        salarySendNew.setLastSendTime(sendTime);

        mapper.updateById(salarySendNew);

        List<SalarySendInfoPO> needSendList = Lists.newArrayList();
        enableSendList.forEach(map -> {
            sendList.forEach(item -> {
                if (item.getId() == Long.valueOf(map.get("id").toString()).longValue()) {
                    needSendList.add(item);
                }
            });
        });

        // 发送消息
        param.setTemplate(templatePO);
        sendMessage(true, needSendList, param, salarySend);

        Map<String, Object> map = new HashMap<>(2);
        map.put("sendNum", sendNum);
        map.put("sendTotal", sendTotal);

        return map;
    }

    /**
     * 发送消息
     *
     * @param sendFlag   true 发送 false 撤回
     * @param list
     * @param param
     * @param salarySend
     */
    private void sendMessage(boolean sendFlag, List<SalarySendInfoPO> list, SalarySendGrantParam param, SalarySendPO salarySend) {
        new Thread() {
            public void run() {
                List<SalarySendInfoPO> pos = list;
                if (CollectionUtils.isNotEmpty(param.getIds())) {
                    pos = list.stream().filter(f -> param.getIds().contains(f.getId())).collect(Collectors.toList());
                }
                pos.stream().filter(po -> !Objects.equals(po.getEmployeeType(), 1)).forEach(po -> {
                    Long employeeId = po.getEmployeeId();
                    if (sendFlag) {
                        sendPayRollEMMessage(po, param.getTemplate(), employeeId);
                    } else {
                        withdrawPayrollEMMessage(po, employeeId);
                    }
                });
            }
        }.start();
    }

    /**
     * 发送Em消息
     */
    private void sendPayRollEMMessage(SalarySendInfoPO po, SalaryTemplatePO template, Long employeeId) {

//        try {
//            MessageUtil.sendSMS(mobile, content);
//        } catch (Exception e) {
//            kqLog.info("SendSMS error.resourceId:" + resourceId + ">>>>>>mobile>>>>>>" + mobile);
//            log.writeLog(e);
//        }


        Long id = po.getId();
        String background = template.getBackground();
        String billTitle = getBillTitle(template.getTheme(), po.getSalaryMonth(), employeeId);
        MessageType messageType = MessageType.newInstance(499); // 消息来源（见文档第四点补充 必填）
        Set<String> userIdList = new HashSet<>(); // 接收人id 必填
        userIdList.add(employeeId.toString());
        String title = billTitle; // 标题
        String context = "点击查看详情"; // 内容
        // PC端链接
        String linkUrl = weaver.general.GCONST.getContextPath() + "/spa/hrmSalary/static/index.html#/main/hrmSalary/mobilepayroll?id=" + id + "&recipient=" + employeeId;
        // 移动端链接
        String linkMobileUrl = weaver.general.GCONST.getContextPath() + "/spa/hrmSalary/static/index.html#/main/hrmSalary/mobilepayroll?type=phone&id=" + id + "&recipient=" + employeeId;
        try {
            MessageBean messageBean = Util_Message.createMessage(messageType, userIdList, title, context, linkUrl, linkMobileUrl);
            messageBean.setCreater(user.getUID());// 创建人id
            messageBean.setBizState("0");// 需要修改消息为已处理等状态时传入,表示消息最初状态为待处理
            messageBean.setTargetId("499|" + id); //消息来源code +“|”+业务id需要修改消息为已处理等状态时传入

            if (StringUtils.isNotBlank(background)) {
                messageBean.setPictureUrl(background);
            } else {
                messageBean.setPictureUrl("/hrm/hrm_e9/images/payroll.jpg?pictype=jpg");
            }
            Util_Message.store(messageBean);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    /**
     * 撤回EM消息
     *
     * @param po
     */
    private void withdrawPayrollEMMessage(SalarySendInfoPO po, Long employeeId) {
        try {
            MessageBean messageBean = Util_Message.createMessage();
            messageBean.setUserList(new HashSet<>());//接收人id
            messageBean.setUserId(employeeId.intValue());
            messageBean.setTargetId("499|" + po.getId()); //code + “|” + 业务id
            //messageBean.setMessageType(MessageType.newInstance(121));//消息来源code(传了代表code也做为删除时的条件，默认不传）
            Util_Message.delMessageTargetid(messageBean);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    private List<Map<String, Object>> getEnableSendList(SalarySendPO salarySend, Collection<Long> ids, SalaryTemplatePO salaryTemplate) {
        // 1.根据模板获取薪资项目设置
        SalaryAcctRecordPO salaryAcctRecord = getSalaryAcctRecordService(user).getById(salarySend.getSalaryAccountingId());
        boolean isReplenish = salaryAcctRecord != null && Objects.equals(salaryAcctRecord.getBackCalcStatus(), (NumberUtils.INTEGER_ONE));
        List<SalaryTemplateSalaryItemListDTO> salaryItems = getSalaryItemsSetting(salaryTemplate, isReplenish);
        SalarySendDetailQueryParam queryParam = new SalarySendDetailQueryParam();
        queryParam.setSalarySendId(salarySend.getId());
        // 空就是所有
        queryParam.setIds(ids);
        queryParam.setSendStatuss(Arrays.asList(SalarySendStatusEnum.UNSEND.getValue(), SalarySendStatusEnum.WITHDRAW.getValue()));
        // 2.获取基本数据
        List<SalarySendDetailListDTO> list = salarySendInfoMapper.detailList(queryParam, null);
        // 3.组装详情数据
        List<Map<String, Object>> listMaps = buildDetailList(salaryItems, list, salarySend.getSalaryAccountingId());
        return listMaps;
    }


    @Override
    public Map<String, Object> withdraw(SalarySendWithdrawParam param) {
        if (param.getSalarySendId() == null) {
            throw new SalaryRunTimeException(SalaryI18nUtil.getI18nLabel(100327, "工资单发放Id必传"));
        }

        Long salarySendId = param.getSalarySendId();
        Collection<Long> ids = param.getIds();

        // 获取可撤回的工资单
        if (param.getSalarySendRangeIds() != null) {
            //如果传了范围id，则使用范围id撤回
            ids = getSalarySendRangeService(user).getSendInfoIdsBySendId(salarySendId, param.getSalarySendRangeIds(), SalarySendGrantTypeEnum.WITHDRAW);
            if (ids.isEmpty()) {// 由于查出来是空的，会导致全部发放，在此进行拦截
                throw new SalaryRunTimeException("工资撤回范围内没有匹配的员工");
            }
        }

        SalarySendPO salarySend = mapper.getById(salarySendId);
        if (salarySend == null) {
            throw new SalaryRunTimeException(SalaryI18nUtil.getI18nLabel(100512, "工资单发放不存在"));
        }

        // 撤回
        SalarySendInfoPO po = new SalarySendInfoPO();
        po.setSendStatus(SalarySendStatusEnum.WITHDRAW.getValue());
        po.setBillReadStatus(BillReadStatusEnum.UNREAD.getValue());
        po.setBillConfirmStatus(BillConfimStatusEnum.UNCONFIRMED.getValue());
        List<Integer> statusList = new ArrayList<>();
        statusList.add(SalarySendStatusEnum.ALREADYSEND.getValue());
        salarySendInfoMapper.updateGrantWithdraw(po, salarySendId, statusList, ids);


        po = new SalarySendInfoPO();
        po.setDeleteType(0);
        po.setSalarySendId(salarySendId);
        List<SalarySendInfoPO> list = salarySendInfoMapper.listSome(po);

        int sendNum = (int) list.stream().filter(e -> e.getSendStatus().equals(SalarySendStatusEnum.ALREADYSEND.getValue())).count();
        int sendTotal = list.size();

        SalarySendPO salarySendNew = new SalarySendPO();
        try {
            BeanUtils.copyProperties(salarySendNew, salarySend);
        } catch (Exception e) {
            throw new SalaryRunTimeException("数据转换异常, " + e.getMessage());
        }
        salarySendNew.setSendNum(sendNum);
        salarySendNew.setSendTotal(sendTotal);
        mapper.updateById(salarySendNew);

        SalarySobPO salarySob = getSalarySobService(user).getById(salarySend.getSalarySobId());
        // 记录日志
        LoggerContext loggerContext = new LoggerContext();
        loggerContext.setTargetId(String.valueOf(salarySend.getId()));
        loggerContext.setTargetName(SalaryDateUtil.getFormatYearMonth(salarySend.getSalaryMonth()) + "-" + (salarySob == null ? "" : salarySob.getName()));
        loggerContext.setOperateType(OperateTypeEnum.UPDATE.getValue());
        loggerContext.setOperateTypeName(SalaryI18nUtil.getI18nLabel(0, "撤回工资单"));
        loggerContext.setOperatedesc(SalaryI18nUtil.getI18nLabel(0, "撤回工资单"));
        loggerContext.setOldValues(salarySend);
        loggerContext.setNewValues(salarySendNew);
        loggerContext.setOperator(user.getUID() + StringUtils.EMPTY);
        loggerContext.setOperatorName(Objects.isNull(user) ? StringUtils.EMPTY : user.getUsername());
        SalaryElogConfig.salarySendLoggerTemplate.write(loggerContext);


        SalarySendGrantParam grantParam = SalarySendGrantParam.builder().ids(param.getIds()).salarySendId(param.getSalarySendId()).build();

        // 异步发送消息：先修改数据再发消息，避免出错后无法撤回撤回消息
        sendMessage(false, list, grantParam, salarySend);


        Map<String, Object> map = new HashMap<>(2);
        map.put("sendNum", sendNum);
        map.put("sendTotal", sendTotal);
        return map;
    }


    @Override
    public XSSFWorkbook exportInfoList(SalarySendInfoQueryParam queryParam) {
        // 校验
        SalarySendInfoQueryParam.checkParam(queryParam);

        // 1.工作簿名称
        String sheetName = SalaryI18nUtil.getI18nLabel(100528, "工资单发放信息");
        List<Object> header = new ArrayList<>();
        header.add(SalaryI18nUtil.getI18nLabel(85429, "姓名"));
        header.add(SalaryI18nUtil.getI18nLabel(86184, "个税扣缴义务人"));
        header.add(SalaryI18nUtil.getI18nLabel(86185, "部门"));
        header.add(SalaryI18nUtil.getI18nLabel(86186, "手机号"));
        header.add(SalaryI18nUtil.getI18nLabel(86317, "工号"));
        header.add(SalaryI18nUtil.getI18nLabel(86317, "发送状态"));
        SalarySendPO salarySendPO = getById(queryParam.getSalarySendId());
        if (salarySendPO == null) {
            throw new SalaryRunTimeException("工资单不存在");
        }
        List<SalaryTemplatePO> salaryTemplates = getSalaryTemplateService(user).getDefaultTemplates(Collections.singletonList(salarySendPO.getSalarySobId()));
        if (CollectionUtils.isEmpty(salaryTemplates)) {
            throw new SalaryRunTimeException("请先设置工资单模板");
        }
        Integer ackFeedbackStatus = salaryTemplates.get(0).getAckFeedbackStatus();
        Integer feedbackStatus = salaryTemplates.get(0).getFeedbackStatus();
        if ((ackFeedbackStatus != null && NumberUtils.compare(ackFeedbackStatus, 1) == 0)
                || (feedbackStatus != null && NumberUtils.compare(feedbackStatus, 1) == 0)) {
            header.add(SalaryI18nUtil.getI18nLabel(0, "查看状态"));
            header.add(SalaryI18nUtil.getI18nLabel(0, "确认状态"));
        }
        List<List<Object>> rows = new ArrayList<>();
        // 2.表头
        rows.add(header);
        // 获取行数据
        //排序配置
        OrderRuleVO orderRule = getSalarySysConfService(user).orderRule();
        queryParam.setOrderRule(orderRule);
        List<SalarySendInfoListDTO> list = salarySendInfoMapper.list(queryParam);
        List<DataCollectionEmployee> employeeList = getExtEmpService(user).listEmployee();
        Map<Long, List<DataCollectionEmployee>> idEmpMap = SalaryEntityUtil.group2Map(employeeList, DataCollectionEmployee::getEmployeeId);
        list.forEach(e -> {
            // 发放状态
            e.setSendStatus(SalarySendStatusEnum.getDefaultLabelByValue(Integer.valueOf(e.getSendStatus())));
            // 查看状态
            e.setBillReadStatus(NumberUtils.isCreatable(e.getBillReadStatus()) ? BillReadStatusEnum.getDefaultLabelByValue(Integer.valueOf(e.getBillReadStatus())) : "");
            // 确认状态
            e.setBillConfirmStatus(NumberUtils.isCreatable(e.getBillConfirmStatus()) ? BillConfimStatusEnum.getDefaultLabelByValue(Integer.valueOf(e.getBillConfirmStatus())) : "");
            //外部人员
            if (Objects.equals(e.getEmployeeType(), 1)) {
                List<DataCollectionEmployee> employees = idEmpMap.getOrDefault(e.getEmployeeId(), new ArrayList<>());
                if (CollectionUtils.isNotEmpty(employees) && employees.size() == 1) {
                    DataCollectionEmployee employee = employees.get(0);
                    e.setUsername(employee.getUsername());
                    e.setDepartment(employee.getDepartmentName());
                    e.setMobile(employee.getMobile());
                    e.setJobNum(employee.getWorkcode());
                }
            }
        });

        // 3.添加行记录
        // 3.表数据
        for (SalarySendInfoListDTO dto : list) {
            List<Object> row = new ArrayList<>();
            row.add(dto.getUsername());
            row.add(dto.getTaxAgent());
            row.add(dto.getDepartment());
            row.add(dto.getMobile());
            row.add(dto.getJobNum());
            row.add(dto.getSendStatus());
            if ((ackFeedbackStatus != null && NumberUtils.compare(ackFeedbackStatus, 1) == 0)
                    || (feedbackStatus != null && NumberUtils.compare(feedbackStatus, 1) == 0)) {
                row.add(dto.getBillReadStatus());
                row.add(dto.getBillConfirmStatus());
            }
            rows.add(row);
        }

        // 记录操作日志
        SalarySendPO salarySend = mapper.getById(queryParam.getSalarySendId());
        SalarySobPO salarySob = getSalarySobService(user).getById(salarySend.getSalarySobId());

        String name = SalaryDateUtil.getFormatYearMonth(salarySend.getSalaryMonth()) + "-" + (salarySob == null ? "" : salarySob.getName());
        LoggerContext<AddUpSituation> loggerContext = new LoggerContext<>();
        loggerContext.setTargetName(name);
        loggerContext.setOperateType(OperateTypeEnum.EXCEL_EXPORT.getValue());
        loggerContext.setOperateTypeName(name);
        loggerContext.setOperatedesc(SalaryI18nUtil.getI18nLabel(0, "工资单发放") + ":" + name + SalaryI18nUtil.getI18nLabel(0, " 导出"));
        loggerContext.setUser(user);
        SalaryElogConfig.salarySendLoggerTemplate.write(loggerContext);
        return ExcelUtil.genWorkbookV2(rows, sheetName);
    }

//    @BatchExportHandler("exportSalarySendInfo")
//    public void salarySendInfoExportHandler() {
//        BatchCallbackMessage message = BatchExportContext.getBatchCallbackMessage();
//        System.out.println("接受到导出的结果" + JSONObject.toJSONString(message));
//    }

    @Override
    public XSSFWorkbook exportDetailList(SalarySendPO salarySend, SalarySendDetailQueryParam queryParam) {

        // 1.根据模板获取薪资项目设置
        SalaryAcctRecordPO salaryAcctRecord = getSalaryAcctRecordService(user).getById(salarySend.getSalaryAccountingId());
        boolean isReplenish = salaryAcctRecord != null && Objects.equals(salaryAcctRecord.getBackCalcStatus(), (NumberUtils.INTEGER_ONE));
        List<SalaryTemplatePO> salaryTemplates = getSalaryTemplateService(user).getDefaultTemplates(Collections.singletonList(salarySend.getSalarySobId()));
        List<SalaryTemplateSalaryItemListDTO> salaryItems = getSalaryItemsSetting(CollectionUtils.isNotEmpty(salaryTemplates) ? salaryTemplates.get(0) : null, isReplenish);

        // 2.获取基本数据
        //排序配置
        OrderRuleVO orderRule = getSalarySysConfService(user).orderRule();
        queryParam.setOrderRule(orderRule);

        List<SalarySendDetailListDTO> list = salarySendInfoMapper.detailList(queryParam, null);

        // 3.组装详情数据
        List<Map<String, Object>> listMaps = buildDetailList(salaryItems, list, salarySend.getSalaryAccountingId());

        // 1.工作簿名称
        String sheetName = SalaryI18nUtil.getI18nLabel(97036, "工资单发放详情");
        List<Object> header = new ArrayList<>();
        // 员工信息字段
        header.add(SalaryI18nUtil.getI18nLabel(85429, "姓名"));
        header.add(SalaryI18nUtil.getI18nLabel(86185, "部门"));
        header.add(SalaryI18nUtil.getI18nLabel(86186, "手机号"));
        header.add(SalaryI18nUtil.getI18nLabel(86317, "工号"));
        Map<String, SalaryTemplateSalaryItemListDTO> salaryItemMap = SalaryEntityUtil.convert2Map(salaryItems, dto -> dto.getSalaryItemId() + SalaryConstant.DYNAMIC_SUFFIX);
        List<SalaryTemplateSalaryItemListDTO> sortedSalaryItems = new ArrayList<>();
        for (String column : queryParam.getColumns()) {
            if (column.contains(SalaryConstant.DYNAMIC_SUFFIX)) {
                SalaryTemplateSalaryItemListDTO salaryTemplateSalaryItemListDTO = salaryItemMap.get(column);
                if (salaryTemplateSalaryItemListDTO != null) {
                    sortedSalaryItems.add(salaryTemplateSalaryItemListDTO);
                }
            }
        }

        // 动态列
        if (org.apache.commons.collections4.CollectionUtils.isNotEmpty(sortedSalaryItems)) {
            for (SalaryTemplateSalaryItemListDTO salaryItem : sortedSalaryItems) {
                header.add(salaryItem.getName());
            }
        }
        List<List<Object>> rows = new ArrayList<>();

        // 2.表头
        rows.add(header);

        // 3.表数据
        for (Map<String, Object> dto : listMaps) {
            List<Object> row = new ArrayList<>();
            row.add(dto.get("username"));
            row.add(dto.get("department"));
            row.add(dto.get("mobile"));
            row.add(dto.get("jobNum"));

            // 动态列
            Map<String, Object> mapColumn = listMaps.get(0);
            for (SalaryTemplateSalaryItemListDTO salaryItem : sortedSalaryItems) {
                if (mapColumn.containsKey(salaryItem.getId() + SalaryItemConstant.DYNAMIC_SUFFIX)) {
                    row.add(dto.get(salaryItem.getId() + SalaryItemConstant.DYNAMIC_SUFFIX));
                }
            }

            rows.add(row);
        }

        // 记录日志
        SalarySobPO salarySob = getSalarySobService(user).getById(salaryAcctRecord.getSalarySobId());
        LoggerContext loggerContext = new LoggerContext();
        loggerContext.setTargetId(String.valueOf(salarySend.getId()));
        loggerContext.setTargetName(SalaryDateUtil.getFormatYearMonth(salarySend.getSalaryMonth()) + "-" + (salarySob == null ? "" : salarySob.getName()));
        loggerContext.setOperateType(OperateTypeEnum.EXCEL_EXPORT.getValue());
        loggerContext.setOperateTypeName(SalaryI18nUtil.getI18nLabel(0, "导出工资单"));
        loggerContext.setOperatedesc(SalaryI18nUtil.getI18nLabel(0, "导出工资单"));
        loggerContext.setOperator(user.getUID() + StringUtils.EMPTY);
        loggerContext.setOperatorName(Objects.isNull(user) ? StringUtils.EMPTY : user.getUsername());
        SalaryElogConfig.salarySendLoggerTemplate.write(loggerContext);

        return ExcelUtil.genWorkbookV2(rows, sheetName);
    }

    @Override
    public List<SalarySendCheckDTO> getSalarySendCheckResult(Set<Long> salaryAcctRecordIds) {
        if (CollectionUtils.isEmpty(salaryAcctRecordIds)) {
            return Collections.emptyList();
        }
        Map<String, Object> condition = new HashMap<>();
        condition.put("salaryAccountingIds", salaryAcctRecordIds);
        List<SalarySendPO> salarySendPOS = getSalarySendMapper().listSomeWithCondition(condition);
        Map<Long, SalarySendPO> map = SalaryEntityUtil.convert2Map(salarySendPOS, SalarySendPO::getSalaryAccountingId);

        return salaryAcctRecordIds.stream().map(m -> {
            SalarySendPO salarySend = map.get(m);
            SalarySendCheckDTO salarySendCheck = new SalarySendCheckDTO();
            salarySendCheck.setSalaryAcctId(m);
            // 是否存在已经发送
            salarySendCheck.setAlreadySend(salarySend != null && salarySend.getSendNum() > 0);
            // 是否已经发送完成
            salarySendCheck.setSendFinished(salarySend != null && Objects.equals(salarySend.getSendNum(), salarySend.getSendTotal()));
            return salarySendCheck;
        }).collect(Collectors.toList());
    }

    @Override
    public void handleHistory() {
        mapper.batchHandleSendStatusHistory();
        mapper.batchHandleSalaryAcctTypeHistory();
    }


    @Override
    public Map<String, Object> sumRow(SalarySendInfoQueryParam param) {
        Long salarySendId = param.getSalarySendId();
        // 获取薪资核算ID
        SalarySendPO salarySendPO = getById(salarySendId);
        if (Objects.isNull(salarySendPO)) {
            throw new SalaryRunTimeException("工资发放记录不存在或已被删除");
        }

        SalaryAcctResultQueryParam queryParam = SalaryAcctResultQueryParam.builder().salaryAcctRecordId(salarySendPO.getSalaryAccountingId()).build();
        // 查询薪资核算人员
        List<SalaryAcctEmployeePO> salaryAcctEmployeePOS = getSalaryAcctEmployeeService(user).listByResultQueryParam(queryParam);

        if (org.apache.commons.collections4.CollectionUtils.isEmpty(salaryAcctEmployeePOS)) {
            return Collections.emptyMap();
        }
        // 查询薪资核算记录
        SalaryAcctRecordPO salaryAcctRecordPO = getSalaryAcctRecordService(user).getById(queryParam.getSalaryAcctRecordId());
        if (Objects.isNull(salaryAcctRecordPO)) {
            throw new SalaryRunTimeException(SalaryI18nUtil.getI18nLabel(98747, "薪资核算记录不存在或已被删除"));
        }
        // 查询薪资核算所用薪资账套的薪资项目
        List<SalarySobItemPO> salarySobItemPOS = getSalarySobItemService(user).listBySalarySobId(salaryAcctRecordPO.getSalarySobId());
        Map<Long, SalarySobItemPO> salaryItemIdKeySalarySobItemPOMap = SalaryEntityUtil.convert2Map(salarySobItemPOS, SalarySobItemPO::getId);
        Set<Long> salaryItemIds = SalaryEntityUtil.properties(salarySobItemPOS, SalarySobItemPO::getSalaryItemId);
        if (Objects.equals(salaryAcctRecordPO.getBackCalcStatus(), NumberUtils.INTEGER_ONE)) {
            // 是回算，获取回算项
            List<SalarySobBackItemPO> salarySobBackItemPOS = getSalarySobBackItemService(user).listBySalarySobId(salaryAcctRecordPO.getSalarySobId());
            salaryItemIds.addAll(salarySobBackItemPOS.stream().map(SalarySobBackItemPO::getSalaryItemId).collect(Collectors.toList()));
        }
        List<SalaryItemPO> salaryItemPOS = getSalaryItemService(user).listByIds(salaryItemIds);

        // 查询薪资核算结果
        List<Long> salaryAcctEmployeeIds = SalaryEntityUtil.properties(salaryAcctEmployeePOS, SalaryAcctEmployeePO::getId, Collectors.toList());
        List<SalaryAcctResultPO> salaryAcctResultPOS = getSalaryAcctResultService(user).listBySalaryAcctEmployeeIds(salaryAcctEmployeeIds);
        // 是否是补发工资单
        List<SalarySendInfoPO> salarySendInfoPOS = getSalarySendInfoMapper().listSome(SalarySendInfoPO.builder().salarySendId(salarySendId).build());
        List<Long> sendEmployeeIds = salarySendInfoPOS.stream().map(SalarySendInfoPO::getEmployeeId).collect(Collectors.toList());
        salaryAcctResultPOS = salaryAcctResultPOS.stream().filter(po -> sendEmployeeIds.contains(po.getEmployeeId())).collect(Collectors.toList());

        Map<String, Object> map = new HashMap<>();
        Map<Long, List<SalaryAcctResultPO>> acctResultMap = SalaryEntityUtil.group2Map(salaryAcctResultPOS, SalaryAcctResultPO::getSalaryItemId);
        salaryItemPOS.stream().filter(item -> SalaryDataTypeEnum.NUMBER.getValue().equals(item.getDataType())).forEach(item -> {
            BigDecimal sum = Optional.ofNullable(acctResultMap.get(item.getId())).orElse(new ArrayList<>()).stream().map(SalaryAcctResultPO::getResultValue).filter(NumberUtils::isCreatable).map(BigDecimal::new).reduce(BigDecimal.ZERO, BigDecimal::add);
            map.put(item.getId() + SalaryItemConstant.DYNAMIC_SUFFIX, SalaryAcctFormulaBO.roundResultValue(sum.toString(), item, Collections.emptyList(), Collections.emptyMap(), salaryItemIdKeySalarySobItemPOMap));
        });
        return map;
    }

    @Override
    public void deleteBySalaryAcctRecordIds(Collection<Long> ids) {
        if (CollectionUtils.isNotEmpty(ids)) {
            getSalarySendMapper().deleteBySalaryAcctRecordIds(ids);
            getSalarySendInfoMapper().deleteBySalaryAcctRecordIds(ids);
        }
    }

    @Override
    public void sendMobileCode(SMSCodeSendParam param) {
        Long id = param.getId();
        SalarySendInfoPO po = getSalarySendInfoMapper().getById(id);
        if (po == null) {
            throw new SalaryRunTimeException("未获取工资单发放信息");
        }

        Long employeeId = po.getEmployeeId();
        DataCollectionEmployee employee = getSalaryEmployeeService(user).getEmployeeById(employeeId);
        if (employee == null) {
            throw new SalaryRunTimeException("未获取人员信息");
        }

        String mobile = employee.getMobile();
        if (Validator.isMobile(mobile)) {
            throw new SalaryRunTimeException("手机号信息有误");
        }

        boolean checkSendSMS = MessageUtil.checkSendSMS();
        if (!checkSendSMS) {
            throw new SalaryRunTimeException("短信服务异常");
        }

        //1、生成6位验证码
        String mobileCode = (int) ((Math.random() * 9 + 1) * 100000) + "";
        //失效时间
        long expirationTime = LocalDateTime.now().plusMinutes(10L).toInstant(ZoneOffset.ofHours(8)).toEpochMilli();
        String cacheValue = mobileCode + "_" + expirationTime;

        //2、验证码缓存，10分钟失效
        getSalaryCacheService(user).set(SALARY_CACHE_SMS_CODE + "_" + id, cacheValue);

        //3、发送短信
        MessageUtil.sendSMS(mobile, "验证码：" + mobileCode + "有效时间10分钟，用于查看工资单，请不要告诉他人。");
    }

    @Override
    public Boolean checkMobileCode(SMSCodeCheckParam param) {
        Long id = param.getId();

        String mobileCode = param.getMobileCode();

        //取出验证码
        String cacheValue = getSalaryCacheService(user).get(SALARY_CACHE_SMS_CODE + "_" + id);
        String[] cache = cacheValue.split("_");
        String code = cache[0];
        //失效时间
        long expirationTime = Long.parseLong(cache[1]);

        long nowTime = LocalDateTime.now().toInstant(ZoneOffset.ofHours(8)).toEpochMilli();
        if (nowTime > expirationTime) {
            throw new SalaryRunTimeException("验证码已失效，请重新发送");
        }

        if (!StringUtils.equals(code, mobileCode)) {
            throw new SalaryRunTimeException("验证码错误");
        }

        return true;
    }

    @Override
    public List<SalarySendInfoPO> getNeedSendInfoList(List<Long> salarySendIds) {
        if (CollectionUtils.isEmpty(salarySendIds)) {
            return Collections.emptyList();
        }
        return getSalarySendInfoMapper().getNeedSendInfoList(salarySendIds);
    }

    @Override
    public List<SalarySendPO> getNeedSendListBySalarySobIds(List<Long> salarySobIds) {
        if (CollectionUtils.isEmpty(salarySobIds)) {
            return Collections.emptyList();
        }
        return getSalarySendMapper().getNeedSendListBySalarySobIds(salarySobIds);
    }

    @Override
    public List<SalarySendInfoPO> listUnConfirmedSendInfo(List<Long> salarySobIds) {
        if (CollectionUtils.isEmpty(salarySobIds)) return Collections.emptyList();
        return getSalarySendInfoMapper().listUnConfirmedSendInfo(salarySobIds);
    }

    @Override
    public void autoConfirmSalaryBill(List<Long> needAutoSendIds) {
        if (CollectionUtils.isEmpty(needAutoSendIds)) {
            return;
        }
        List<List<Long>> partition = Lists.partition((List) needAutoSendIds, 500);
        partition.forEach(getSalarySendInfoMapper()::autoConfirmSalaryBill);
    }

    @Override
    public List<SalarySendPO> getByIds(List<Long> salarySendId) {
        if (CollectionUtils.isEmpty(salarySendId)) {
            return Collections.emptyList();
        }
        return getSalarySendMapper().getByIds(salarySendId);
    }

    @Override
    public List<SalarySendPO> listSome(SalarySendPO param) {
        return getSalarySendMapper().listSome(param);
    }

    @Override
    public Map<String, Object> preview(SalaryPreviewParam param) {
        Long salaryInfoId = param.getSalaryInfoId();
        Long recipient = param.getRecipient();

        if (salaryInfoId == null) {
            throw new SalaryRunTimeException("工资单id为空！");
        }

        SalarySendInfoPO salarySendInfo = getSalarySendInfoMapper().getById(salaryInfoId);
        if (salarySendInfo == null) {
            throw new SalaryRunTimeException(SalaryI18nUtil.getI18nLabel(100511, "工资单信息不存在"));
        }

        // 获取默认模板信息
        SalarySendPO salarySendPO = getSalarySendMapper().getById(salarySendInfo.getSalarySendId());
        if (salarySendPO == null) {
            throw new SalaryRunTimeException("工资单记录不存在");
        }

        // 获取模板
        SalarySobPO salarySob = getSalarySobService(user).getById(salarySendPO.getSalarySobId());
        TaxAgentPO taxAgentPO = getTaxAgentService(user).getById(salarySendInfo.getTaxAgentId());
        List<SalaryTemplatePO> salaryTemplates = getSalaryTemplateService(user).getDefaultTemplates(Arrays.asList(salarySendPO.getSalarySobId(), salarySob.getId()));
        if (CollectionUtils.isEmpty(salaryTemplates)) {
            throw new SalaryRunTimeException(SalaryI18nUtil.getI18nLabel(100518, "没有默认模板，无法发送"));
        }
        SalaryBillSendDTO salaryBillSendDTO = getSalaryBillService(user).buildSendParams(salarySendPO, salaryTemplates.get(0));

        SalaryTemplatePO salaryTemplate = salaryBillSendDTO.getSalaryTemplate();
        if (salaryTemplate == null) {
            throw new SalaryRunTimeException(SalaryI18nUtil.getI18nLabel(100513, "没有默认模板，无法查看"));
        }

        // 判断是否是补发
        boolean isReplenish = NumberUtils.INTEGER_ONE.equals(salarySendInfo.getSalaryAcctType());

        SalaryAcctResultPO build = SalaryAcctResultPO.builder()
                .salaryAcctRecordId(salarySendInfo.getSalaryAcctRecordId())
                .taxAgentId(salarySendInfo.getTaxAgentId())
                .employeeId(recipient)
                .build();
        List<SalaryAcctResultPO> salaryAcctResultPOS = getSalaryAcctResultMapper().listSome(build);
        if (CollUtil.isEmpty(salaryAcctResultPOS)) {
            throw new SalaryRunTimeException("薪资核算结果不存在！");
        }
        encryptUtil.decryptList(salaryAcctResultPOS, SalaryAcctResultPO.class);

        SalaryAcctRecordPO salaryAcctRecordPO = getSalaryAcctRecordService(user).getById(salarySendInfo.getSalaryAcctRecordId());
        if (salaryAcctRecordPO == null) {
            throw new SalaryRunTimeException("薪资核算记录不存在！");
        }
        List<Map<String, Object>> salaryAcctResultS = null;
        if (Objects.equals(salaryAcctRecordPO.getBackCalcStatus(), NumberUtils.INTEGER_ONE) && !isReplenish) {
            // 该记录回算过,并且获取的不是回算后的工资单
            salaryAcctResultS = salaryAcctResultPOS.stream().map(m -> {
                Map<String, Object> map = new LinkedHashMap<>();
                map.put("salaryItemId", m.getSalaryItemId());
                map.put("resultValue", m.getOriginResultValue());
                return map;
            }).collect(Collectors.toList());
        } else {
            salaryAcctResultS = salaryAcctResultPOS.stream().map(m -> {
                Map<String, Object> map = new LinkedHashMap<>();
                map.put("salaryItemId", m.getSalaryItemId());
                map.put("resultValue", m.getResultValue());
                return map;
            }).collect(Collectors.toList());
        }


        Map<String, Object> map = new LinkedHashMap<>();
        map.put("tenantName", "");
        map.put("sendTime", SalaryDateUtil.getFormatLocalDateTime(salarySendInfo.getSendTime()));
        List<SalaryTemplateSalaryItemSetListDTO> listDTOS = JSONArray.parseArray(isReplenish ? salaryTemplate.getReplenishSalaryItemSetting() : salaryTemplate.getSalaryItemSetting(), SalaryTemplateSalaryItemSetListDTO.class);
        Optional<SalaryTemplateSalaryItemSetListDTO> optionalEmployeeInformation = listDTOS.stream().filter(e -> SalaryTemplateSalaryItemSetGroupConstant.EMPLOYEE_INFO_GROUP_ID.equals(e.getGroupId())).findFirst();
        SalaryTemplateSalaryItemSetListDTO employeeInformation = optionalEmployeeInformation.orElse(null);
        List<SalaryTemplateSalaryItemSetListDTO> itemSetListDTOS = listDTOS.stream().filter(e -> !SalaryTemplateSalaryItemSetGroupConstant.EMPLOYEE_INFO_GROUP_ID.equals(e.getGroupId())).collect(Collectors.toList());
        List<Map<String, Object>> finalSalaryAcctResultS = salaryAcctResultS;
        itemSetListDTOS.stream().forEach(item -> {
            item.getItems().forEach(e -> {
                if (CollectionUtils.isEmpty(finalSalaryAcctResultS)) {
                    e.setSalaryItemValue("");
                } else {
                    Object o = finalSalaryAcctResultS.stream().filter(f -> f.get("salaryItemId") != null && String.valueOf(f.get("salaryItemId")).equals(e.getSalaryItemId())).findFirst().orElse(new HashMap<>()).get("resultValue");
                    e.setSalaryItemValue(o == null ? "" : (String) o);
                }
            });
        });


        /**
         * 过滤空
         */
        if (Objects.equals(1, salaryTemplate.getSalaryItemNullStatus())) {
            for (SalaryTemplateSalaryItemSetListDTO itemSetListDTO : itemSetListDTOS) {
                List<SalaryTemplateSalaryItemListDTO> items = itemSetListDTO.getItems();
                List<SalaryTemplateSalaryItemListDTO> collect = items.stream().filter(item -> StringUtils.isNotBlank(item.getSalaryItemValue())).collect(Collectors.toList());
                itemSetListDTO.setItems(collect);
            }
        }

        /**
         * 过滤0
         */
        if (Objects.equals(1, salaryTemplate.getSalaryItemZeroStatus())) {
            for (SalaryTemplateSalaryItemSetListDTO itemSetListDTO : itemSetListDTOS) {
                List<SalaryTemplateSalaryItemListDTO> items = itemSetListDTO.getItems();
                List<SalaryTemplateSalaryItemListDTO> collect = items.stream().filter(item -> SalaryEntityUtil.string2BigDecimal(item.getSalaryItemValue()) == null || BigDecimal.ZERO.compareTo(SalaryEntityUtil.string2BigDecimal(item.getSalaryItemValue())) != 0).collect(Collectors.toList());
                itemSetListDTO.setItems(collect);
            }
        }

        DataCollectionEmployee simpleEmployee = getSalaryEmployeeService(user).getEmployeeById(salarySendInfo.getEmployeeId());
        SalaryAcctEmployeePO acctEmployeePO = getSalaryAcctEmployeeService(user).getById(salaryAcctResultPOS.get(0).getSalaryAcctEmpId());
        SalaryAcctEmployeeBO.copyAcctEmp(simpleEmployee, acctEmployeePO);
        buildEmployeeInfo(employeeInformation, simpleEmployee, taxAgentPO.getName(), SalaryAcctResultBO.buildEmployeeFieldName());
        map.put("employeeInformation", employeeInformation);
        map.put("salaryGroups", itemSetListDTOS);

        salaryTemplate.setTheme(getBillTitle(salaryTemplate.getTheme(), salarySendPO.getSalaryMonth(), recipient));
        // 工资单水印文本型动态变量 == 处理
        handleSalaryWatermark(salaryTemplate, salarySendInfo, recipient);
        map.put("salaryTemplate", salaryTemplate);
        map.put("salaryAcctResult", salaryAcctResultS);

        // 工资单确认按钮
        if (salaryTemplate.getAckFeedbackStatus() != null && NumberUtils.compare(salaryTemplate.getAckFeedbackStatus(), 1) == 0) {
            // 开启了工资单确认
            Integer ackStatus = salarySendInfo.getBillConfirmStatus();
            if (ackStatus == null || ackStatus != BillConfimStatusEnum.CONFIRMED.getValue()) {
                map.put("showAck", "1");
            } else {
                map.put("showAck", "0");
            }
            map.put("sendEmployeeId", salarySendInfo.getSendEmployeeId());
        } else {
            map.put("showAck", "0");
        }


        // 工资单反馈
        if (salaryTemplate.getFeedbackStatus() != null && NumberUtils.compare(salaryTemplate.getFeedbackStatus(), 1) == 0) {
            // 开启了工资单反馈按钮
            Integer confirmStatus = salarySendInfo.getBillConfirmStatus();
            map.put("showFeedback", "1");
            // 除非确认状态为已确认否则可以一直反馈
            if (NumberUtils.compare(salaryTemplate.getAckFeedbackStatus(), 1) == 0 && confirmStatus != null && confirmStatus == BillConfimStatusEnum.CONFIRMED.getValue()) {
                map.put("showFeedback", "0");
            }
            map.put("sendEmployeeId", salarySendInfo.getSendEmployeeId());
        } else {
            map.put("showFeedback", "0");
        }

        // 记录查看日志
        String targetName = taxAgentPO.getName() + "-" + SalaryDateUtil.getFormatYearMonth(salarySendInfo.getSalaryMonth());
        LoggerContext<SalaryItemPO> loggerContext = new LoggerContext<>();
        loggerContext.setUser(user);
        loggerContext.setTargetId(String.valueOf(salarySendInfo.getId()));
        loggerContext.setTargetName(targetName);
        loggerContext.setOperateType(OperateTypeEnum.READ.getValue());
        loggerContext.setOperateTypeName(SalaryI18nUtil.getI18nLabel(0, "预览工资单") + ": " + targetName + " " + salaryInfoId);
        loggerContext.setOperatedesc(SalaryI18nUtil.getI18nLabel(0, "预览工资单") + ": " + targetName);
        SalaryElogConfig.mySalaryBillLoggerTemplate.write(loggerContext);
        return map;
    }
}
